diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0a19890aed44..8a0b18a54c0b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @yanglbme @vil02 @BamaCharanChhandogi @alxkm @siriak +* @DenizAltunkapan @yanglbme @vil02 @BamaCharanChhandogi @alxkm @siriak diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 14a4ce5fe0f5..b3969075d668 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,14 +10,14 @@ jobs: uses: actions/setup-java@v4 with: java-version: 21 - distribution: 'adopt' + distribution: 'temurin' - name: Build with Maven run: mvn --batch-mode --update-snapshots verify - name: Upload coverage to codecov (tokenless) if: >- github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: fail_ci_if_error: true - name: Upload coverage to codecov (with token) @@ -25,7 +25,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@v4 + uses: codecov/codecov-action@v5 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 588c05e42e8f..dac697511de1 100644 --- a/.github/workflows/clang-format-lint.yml +++ b/.github/workflows/clang-format-lint.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: DoozyX/clang-format-lint-action@v0.18 + - uses: DoozyX/clang-format-lint-action@v0.20 with: source: './src' extensions: 'java' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ff76b1af452a..a0908a2b57d9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -30,7 +30,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: 21 - distribution: 'adopt' + distribution: 'temurin' - name: Initialize CodeQL uses: github/codeql-action/init@v3 diff --git a/.github/workflows/infer.yml b/.github/workflows/infer.yml index 121a6c728fbb..a07c6f458083 100644 --- a/.github/workflows/infer.yml +++ b/.github/workflows/infer.yml @@ -18,7 +18,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: 21 - distribution: 'adopt' + distribution: 'temurin' - name: Set up OCaml uses: ocaml/setup-ocaml@v3 diff --git a/.gitpod.dockerfile b/.gitpod.dockerfile index f426f0921028..ea6d32a5a377 100644 --- a/.gitpod.dockerfile +++ b/.gitpod.dockerfile @@ -1,4 +1,4 @@ -FROM gitpod/workspace-java-21:2024-09-11-00-04-27 +FROM gitpod/workspace-java-21:2025-02-10-10-54-28 ENV LLVM_SCRIPT="tmp_llvm.sh" diff --git a/DIRECTORY.md b/DIRECTORY.md index 7e5d0d3de9ba..fe9e440da3e2 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -5,11 +5,13 @@ * com * thealgorithms * audiofilters + * [EMAFilter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java) * [IIRFilter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/audiofilters/IIRFilter.java) * backtracking * [AllPathsFromSourceToTarget](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/AllPathsFromSourceToTarget.java) * [ArrayCombination](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java) * [Combination](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/Combination.java) + * [CrosswordSolver](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/CrosswordSolver.java) * [FloodFill](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/FloodFill.java) * [KnightsTour](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/KnightsTour.java) * [MazeRecursion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java) @@ -19,19 +21,41 @@ * [Permutation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/Permutation.java) * [PowerSum](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/PowerSum.java) * [SubsequenceFinder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/SubsequenceFinder.java) + * [WordPatternMatcher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/WordPatternMatcher.java) * [WordSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/WordSearch.java) * bitmanipulation + * [BcdConversion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/BcdConversion.java) + * [BinaryPalindromeCheck](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java) * [BitSwap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/BitSwap.java) + * [BooleanAlgebraGates](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/BooleanAlgebraGates.java) + * [ClearLeftmostSetBit](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/ClearLeftmostSetBit.java) + * [CountLeadingZeros](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/CountLeadingZeros.java) * [CountSetBits](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java) + * [FindNthBit](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/FindNthBit.java) + * [FirstDifferentBit](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/FirstDifferentBit.java) + * [GenerateSubsets](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/GenerateSubsets.java) + * [GrayCodeConversion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/GrayCodeConversion.java) + * [HammingDistance](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/HammingDistance.java) + * [HigherLowerPowerOfTwo](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/HigherLowerPowerOfTwo.java) * [HighestSetBit](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/HighestSetBit.java) * [IndexOfRightMostSetBit](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/IndexOfRightMostSetBit.java) * [IsEven](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/IsEven.java) * [IsPowerTwo](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/IsPowerTwo.java) * [LowestSetBit](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/LowestSetBit.java) + * [ModuloPowerOfTwo](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/ModuloPowerOfTwo.java) + * [NextHigherSameBitCount](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/NextHigherSameBitCount.java) * [NonRepeatingNumberFinder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinder.java) + * [NumberAppearingOddTimes](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/NumberAppearingOddTimes.java) * [NumbersDifferentSigns](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/NumbersDifferentSigns.java) + * [OneBitDifference](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/OneBitDifference.java) + * [OnesComplement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/OnesComplement.java) + * [ParityCheck](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/ParityCheck.java) * [ReverseBits](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/ReverseBits.java) * [SingleBitOperations](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/SingleBitOperations.java) + * [SingleElement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/SingleElement.java) + * [SwapAdjacentBits](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/SwapAdjacentBits.java) + * [TwosComplement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/TwosComplement.java) + * [Xs3Conversion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/Xs3Conversion.java) * ciphers * a5 * [A5Cipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/a5/A5Cipher.java) @@ -40,18 +64,25 @@ * [CompositeLFSR](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/a5/CompositeLFSR.java) * [LFSR](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/a5/LFSR.java) * [Utils](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/a5/Utils.java) + * [ADFGVXCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/ADFGVXCipher.java) * [AES](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/AES.java) * [AESEncryption](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/AESEncryption.java) * [AffineCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/AffineCipher.java) + * [AtbashCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/AtbashCipher.java) * [Autokey](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Autokey.java) + * [BaconianCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/BaconianCipher.java) * [Blowfish](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Blowfish.java) * [Caesar](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Caesar.java) * [ColumnarTranspositionCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java) * [DES](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/DES.java) + * [DiffieHellman](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/DiffieHellman.java) + * [ECC](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/ECC.java) * [HillCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/HillCipher.java) + * [MonoAlphabetic](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/MonoAlphabetic.java) * [PlayfairCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/PlayfairCipher.java) * [Polybius](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Polybius.java) * [ProductCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/ProductCipher.java) + * [RailFenceCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java) * [RSA](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/RSA.java) * [SimpleSubCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/SimpleSubCipher.java) * [Vigenere](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Vigenere.java) @@ -68,19 +99,26 @@ * [DecimalToBinary](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/DecimalToBinary.java) * [DecimalToHexadecimal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/DecimalToHexadecimal.java) * [DecimalToOctal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/DecimalToOctal.java) + * [EndianConverter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/EndianConverter.java) * [HexaDecimalToBinary](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/HexaDecimalToBinary.java) * [HexaDecimalToDecimal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/HexaDecimalToDecimal.java) * [HexToOct](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/HexToOct.java) * [IntegerToEnglish](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/IntegerToEnglish.java) * [IntegerToRoman](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/IntegerToRoman.java) + * [IPConverter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/IPConverter.java) + * [IPv6Converter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/IPv6Converter.java) + * [MorseCodeConverter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/MorseCodeConverter.java) + * [NumberToWords](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/NumberToWords.java) * [OctalToBinary](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/OctalToBinary.java) * [OctalToDecimal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/OctalToDecimal.java) * [OctalToHexadecimal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/OctalToHexadecimal.java) + * [PhoneticAlphabetConverter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/PhoneticAlphabetConverter.java) * [RgbHsvConversion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/RgbHsvConversion.java) * [RomanToInteger](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/RomanToInteger.java) * [TurkishToLatinConversion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java) * [UnitConversions](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/UnitConversions.java) * [UnitsConverter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/UnitsConverter.java) + * [WordsToNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/WordsToNumber.java) * datastructures * bags * [Bag](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/bags/Bag.java) @@ -112,17 +150,20 @@ * [ConnectedComponent](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/ConnectedComponent.java) * [Cycles](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java) * [DijkstraAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithm.java) + * [DijkstraOptimizedAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java) * [EdmondsBlossomAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithm.java) * [FloydWarshall](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/FloydWarshall.java) * [FordFulkerson](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java) * [Graphs](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/Graphs.java) * [HamiltonianCycle](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java) + * [JohnsonsAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithm.java) * [KahnsAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java) * [Kosaraju](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java) * [Kruskal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java) * [MatrixGraphs](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/MatrixGraphs.java) * [PrimMST](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/PrimMST.java) * [TarjansAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithm.java) + * [UndirectedAdjacencyListGraph](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/UndirectedAdjacencyListGraph.java) * [WelshPowell](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/WelshPowell.java) * hashmap * hashing @@ -141,8 +182,11 @@ * [GenericHeap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/GenericHeap.java) * [Heap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/Heap.java) * [HeapElement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/HeapElement.java) + * [KthElementFinder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/KthElementFinder.java) * [LeftistHeap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/LeftistHeap.java) * [MaxHeap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/MaxHeap.java) + * [MedianFinder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/MedianFinder.java) + * [MergeKSortedArrays](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/MergeKSortedArrays.java) * [MinHeap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/MinHeap.java) * [MinPriorityQueue](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/MinPriorityQueue.java) * lists @@ -170,6 +214,9 @@ * [LinkedQueue](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/queues/LinkedQueue.java) * [PriorityQueues](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/queues/PriorityQueues.java) * [Queue](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/queues/Queue.java) + * [QueueByTwoStacks](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/queues/QueueByTwoStacks.java) + * [SlidingWindowMaximum](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/queues/SlidingWindowMaximum.java) + * [TokenBucket](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/queues/TokenBucket.java) * stacks * [NodeStack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/stacks/NodeStack.java) * [ReverseStack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/stacks/ReverseStack.java) @@ -181,6 +228,7 @@ * [AVLSimple](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/AVLSimple.java) * [AVLTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/AVLTree.java) * [BinaryTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/BinaryTree.java) + * [BoundaryTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/BoundaryTraversal.java) * [BSTFromSortedArray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/BSTFromSortedArray.java) * [BSTIterative](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/BSTIterative.java) * [BSTRecursive](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/BSTRecursive.java) @@ -201,13 +249,14 @@ * [PostOrderTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/PostOrderTraversal.java) * [PreOrderTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/PreOrderTraversal.java) * [PrintTopViewofTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/PrintTopViewofTree.java) + * [QuadTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/QuadTree.java) * [RedBlackBST](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/RedBlackBST.java) * [SameTreesCheck](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/SameTreesCheck.java) * [SegmentTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java) * [SplayTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/SplayTree.java) * [Treap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/Treap.java) * [TreeRandomNode](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/TreeRandomNode.java) - * [TrieImp](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/TrieImp.java) + * [Trie](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/Trie.java) * [VerticalOrderTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/VerticalOrderTraversal.java) * [ZigzagTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/ZigzagTraversal.java) * devutils @@ -225,9 +274,15 @@ * divideandconquer * [BinaryExponentiation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/BinaryExponentiation.java) * [ClosestPair](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java) + * [CountingInversions](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/CountingInversions.java) + * [MedianOfTwoSortedArrays](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/MedianOfTwoSortedArrays.java) * [SkylineAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/SkylineAlgorithm.java) * [StrassenMatrixMultiplication](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/StrassenMatrixMultiplication.java) + * [TilingProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/TilingProblem.java) * dynamicprogramming + * [Abbreviation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/Abbreviation.java) + * [AllConstruct](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/AllConstruct.java) + * [AssignmentUsingBitmask](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmask.java) * [BoardPath](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/BoardPath.java) * [BoundaryFill](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/BoundaryFill.java) * [BruteForceKnapsack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsack.java) @@ -252,6 +307,7 @@ * [LongestValidParentheses](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/LongestValidParentheses.java) * [MatrixChainMultiplication](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainMultiplication.java) * [MatrixChainRecursiveTopDownMemoisation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainRecursiveTopDownMemoisation.java) + * [MaximumSumOfNonAdjacentElements](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/MaximumSumOfNonAdjacentElements.java) * [MinimumPathSum](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/MinimumPathSum.java) * [MinimumSumPartition](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/MinimumSumPartition.java) * [NewManShanksPrime](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/NewManShanksPrime.java) @@ -263,23 +319,40 @@ * [ShortestCommonSupersequenceLength](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/ShortestCommonSupersequenceLength.java) * [SubsetCount](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/SubsetCount.java) * [SubsetSum](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSum.java) + * [SubsetSumSpaceOptimized](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimized.java) * [SumOfSubset](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/SumOfSubset.java) + * [TreeMatching](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/TreeMatching.java) * [Tribonacci](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/Tribonacci.java) * [UniquePaths](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/UniquePaths.java) + * [UniqueSubsequencesCount](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCount.java) * [WildcardMatching](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/WildcardMatching.java) * [WineProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java) * geometry + * [BresenhamLine](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/BresenhamLine.java) + * [ConvexHull](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/ConvexHull.java) * [GrahamScan](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/GrahamScan.java) + * [MidpointCircle](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/MidpointCircle.java) + * [MidpointEllipse](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/MidpointEllipse.java) + * [Point](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/Point.java) + * graph + * [ConstrainedShortestPath](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/graph/ConstrainedShortestPath.java) + * [StronglyConnectedComponentOptimized](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java) * greedyalgorithms * [ActivitySelection](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/ActivitySelection.java) + * [BandwidthAllocation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/BandwidthAllocation.java) * [BinaryAddition](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/BinaryAddition.java) * [CoinChange](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/CoinChange.java) * [DigitSeparation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/DigitSeparation.java) + * [EgyptianFraction](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/EgyptianFraction.java) * [FractionalKnapsack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/FractionalKnapsack.java) * [GaleShapley](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/GaleShapley.java) * [JobSequencing](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/JobSequencing.java) + * [KCenters](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/KCenters.java) * [MergeIntervals](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/MergeIntervals.java) * [MinimizingLateness](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/MinimizingLateness.java) + * [MinimumWaitingTime](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTime.java) + * [OptimalFileMerging](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/OptimalFileMerging.java) + * [StockProfitCalculator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/StockProfitCalculator.java) * io * [BufferedReader](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/io/BufferedReader.java) * lineclipping @@ -302,7 +375,9 @@ * [Average](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Average.java) * [BinaryPow](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/BinaryPow.java) * [BinomialCoefficient](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/BinomialCoefficient.java) + * [CatalanNumbers](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/CatalanNumbers.java) * [Ceil](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Ceil.java) + * [ChineseRemainderTheorem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/ChineseRemainderTheorem.java) * [CircularConvolutionFFT](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/CircularConvolutionFFT.java) * [CollatzConjecture](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/CollatzConjecture.java) * [Combinations](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Combinations.java) @@ -317,6 +392,7 @@ * [EulersFunction](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/EulersFunction.java) * [Factorial](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Factorial.java) * [FactorialRecursion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/FactorialRecursion.java) + * [FastExponentiation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/FastExponentiation.java) * [FastInverseSqrt](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/FastInverseSqrt.java) * [FFT](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/FFT.java) * [FFTBluestein](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/FFTBluestein.java) @@ -335,28 +411,26 @@ * [GCD](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/GCD.java) * [GCDRecursion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/GCDRecursion.java) * [GenericRoot](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/GenericRoot.java) + * [GoldbachConjecture](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/GoldbachConjecture.java) * [HarshadNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/HarshadNumber.java) * [HeronsFormula](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/HeronsFormula.java) * [JosephusProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/JosephusProblem.java) * [JugglerSequence](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/JugglerSequence.java) * [KaprekarNumbers](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/KaprekarNumbers.java) + * [KaratsubaMultiplication](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/KaratsubaMultiplication.java) * [KeithNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/KeithNumber.java) * [KrishnamurthyNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java) * [LeastCommonMultiple](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/LeastCommonMultiple.java) * [LeonardoNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/LeonardoNumber.java) * [LinearDiophantineEquationsSolver](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/LinearDiophantineEquationsSolver.java) - * [LiouvilleLambdaFunction](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/LiouvilleLambdaFunction.java) * [LongDivision](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/LongDivision.java) * [LucasSeries](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/LucasSeries.java) * [MagicSquare](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/MagicSquare.java) - * [MatrixRank](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/MatrixRank.java) - * [MatrixUtil](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/MatrixUtil.java) + * [MathBuilder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/MathBuilder.java) * [MaxValue](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/MaxValue.java) * [Means](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Means.java) * [Median](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Median.java) - * [MillerRabinPrimalityCheck](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/MillerRabinPrimalityCheck.java) * [MinValue](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/MinValue.java) - * [MobiusFunction](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/MobiusFunction.java) * [Mode](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Mode.java) * [NonRepeatingElement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/NonRepeatingElement.java) * [NthUglyNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/NthUglyNumber.java) @@ -373,16 +447,22 @@ * [Pow](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Pow.java) * [PowerOfTwoOrNot](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/PowerOfTwoOrNot.java) * [PowerUsingRecursion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/PowerUsingRecursion.java) - * [PrimeCheck](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/PrimeCheck.java) - * [PrimeFactorization](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/PrimeFactorization.java) + * Prime + * [LiouvilleLambdaFunction](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Prime/LiouvilleLambdaFunction.java) + * [MillerRabinPrimalityCheck](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Prime/MillerRabinPrimalityCheck.java) + * [MobiusFunction](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Prime/MobiusFunction.java) + * [PrimeCheck](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Prime/PrimeCheck.java) + * [PrimeFactorization](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Prime/PrimeFactorization.java) + * [SquareFreeInteger](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Prime/SquareFreeInteger.java) * [PronicNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/PronicNumber.java) * [PythagoreanTriple](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/PythagoreanTriple.java) + * [QuadraticEquationSolver](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/QuadraticEquationSolver.java) * [ReverseNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/ReverseNumber.java) * [RomanNumeralUtil](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/RomanNumeralUtil.java) * [SecondMinMax](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SecondMinMax.java) * [SieveOfEratosthenes](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SieveOfEratosthenes.java) * [SimpsonIntegration](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SimpsonIntegration.java) - * [SquareFreeInteger](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SquareFreeInteger.java) + * [SolovayStrassenPrimalityTest](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SolovayStrassenPrimalityTest.java) * [SquareRootWithBabylonianMethod](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SquareRootWithBabylonianMethod.java) * [SquareRootWithNewtonRaphsonMethod](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SquareRootWithNewtonRaphsonMethod.java) * [StandardDeviation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/StandardDeviation.java) @@ -390,34 +470,42 @@ * [StrobogrammaticNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/StrobogrammaticNumber.java) * [SumOfArithmeticSeries](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SumOfArithmeticSeries.java) * [SumOfDigits](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SumOfDigits.java) + * [SumOfOddNumbers](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SumOfOddNumbers.java) * [SumWithoutArithmeticOperators](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SumWithoutArithmeticOperators.java) * [TrinomialTriangle](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/TrinomialTriangle.java) * [TwinPrime](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/TwinPrime.java) + * [UniformNumbers](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/UniformNumbers.java) * [VampireNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/VampireNumber.java) * [VectorCrossProduct](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/VectorCrossProduct.java) * [Volume](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Volume.java) - * matrixexponentiation - * [Fibonacci](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/matrixexponentiation/Fibonacci.java) + * matrix + * [InverseOfMatrix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/matrix/InverseOfMatrix.java) + * matrixexponentiation + * [Fibonacci](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/matrix/matrixexponentiation/Fibonacci.java) + * [MatrixRank](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/matrix/MatrixRank.java) + * [MatrixTranspose](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/matrix/MatrixTranspose.java) + * [MedianOfMatrix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/matrix/MedianOfMatrix.java) + * [MirrorOfMatrix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/matrix/MirrorOfMatrix.java) + * [PrintAMatrixInSpiralOrder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/matrix/PrintAMatrixInSpiralOrder.java) + * [RotateMatrixBy90Degrees](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/matrix/RotateMatrixBy90Degrees.java) + * utils + * [MatrixUtil](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/matrix/utils/MatrixUtil.java) * misc * [ColorContrastRatio](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/ColorContrastRatio.java) - * [InverseOfMatrix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/InverseOfMatrix.java) * [MapReduce](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/MapReduce.java) - * [MatrixTranspose](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/MatrixTranspose.java) - * [MedianOfMatrix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/MedianOfMatrix.java) * [MedianOfRunningArray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/MedianOfRunningArray.java) * [MedianOfRunningArrayByte](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/MedianOfRunningArrayByte.java) * [MedianOfRunningArrayDouble](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/MedianOfRunningArrayDouble.java) * [MedianOfRunningArrayFloat](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/MedianOfRunningArrayFloat.java) * [MedianOfRunningArrayInteger](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/MedianOfRunningArrayInteger.java) * [MedianOfRunningArrayLong](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/MedianOfRunningArrayLong.java) - * [MirrorOfMatrix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/MirrorOfMatrix.java) * [PalindromePrime](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/PalindromePrime.java) * [PalindromeSinglyLinkedList](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java) * [RangeInSortedArray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/RangeInSortedArray.java) + * [ShuffleArray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/ShuffleArray.java) * [Sparsity](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/Sparsity.java) * [ThreeSumProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/ThreeSumProblem.java) * [TwoSumProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/TwoSumProblem.java) - * [WordBoggle](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/WordBoggle.java) * others * [ArrayLeftRotation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/ArrayLeftRotation.java) * [ArrayRightRotation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/ArrayRightRotation.java) @@ -433,7 +521,6 @@ * [CRCAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/CRCAlgorithm.java) * [Damm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/Damm.java) * [Dijkstra](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/Dijkstra.java) - * [FibbonaciSeries](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/FibbonaciSeries.java) * [FloydTriangle](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/FloydTriangle.java) * [GaussLegendre](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/GaussLegendre.java) * [HappyNumbersSeq](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/HappyNumbersSeq.java) @@ -447,6 +534,7 @@ * [LowestBasePalindrome](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java) * [Luhn](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/Luhn.java) * [Mandelbrot](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/Mandelbrot.java) + * [MaximumSlidingWindow](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/MaximumSlidingWindow.java) * [MaximumSumOfDistinctSubarraysWithLengthK](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/MaximumSumOfDistinctSubarraysWithLengthK.java) * [MemoryManagementAlgorithms](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/MemoryManagementAlgorithms.java) * [MiniMaxAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/MiniMaxAlgorithm.java) @@ -457,25 +545,46 @@ * [QueueUsingTwoStacks](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/QueueUsingTwoStacks.java) * [RemoveDuplicateFromString](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/RemoveDuplicateFromString.java) * [ReverseStackUsingRecursion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/ReverseStackUsingRecursion.java) - * [RotateMatrixBy90Degrees](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/RotateMatrixBy90Degrees.java) * [SkylineProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/SkylineProblem.java) - * [Sudoku](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/Sudoku.java) - * [TowerOfHanoi](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/TowerOfHanoi.java) * [TwoPointers](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/TwoPointers.java) * [Verhoeff](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/Verhoeff.java) - * Recursion - * [GenerateSubsets](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/Recursion/GenerateSubsets.java) + * puzzlesandgames + * [Sudoku](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/puzzlesandgames/Sudoku.java) + * [TowerOfHanoi](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java) + * [WordBoggle](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/puzzlesandgames/WordBoggle.java) + * recursion + * [FibonacciSeries](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java) + * [GenerateSubsets](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/recursion/GenerateSubsets.java) * scheduling + * [AgingScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/AgingScheduling.java) + * diskscheduling + * [CircularLookScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/diskscheduling/CircularLookScheduling.java) + * [CircularScanScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/diskscheduling/CircularScanScheduling.java) + * [LookScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/diskscheduling/LookScheduling.java) + * [ScanScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/diskscheduling/ScanScheduling.java) + * [SSFScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/diskscheduling/SSFScheduling.java) + * [EDFScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/EDFScheduling.java) + * [FairShareScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/FairShareScheduling.java) * [FCFSScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/FCFSScheduling.java) + * [GangScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/GangScheduling.java) * [HighestResponseRatioNextScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/HighestResponseRatioNextScheduling.java) + * [JobSchedulingWithDeadline](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/JobSchedulingWithDeadline.java) + * [LotteryScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/LotteryScheduling.java) * [MLFQScheduler](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/MLFQScheduler.java) + * [MultiAgentScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/MultiAgentScheduling.java) + * [NonPreemptivePriorityScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/NonPreemptivePriorityScheduling.java) * [PreemptivePriorityScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/PreemptivePriorityScheduling.java) + * [ProportionalFairScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/ProportionalFairScheduling.java) + * [RandomScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/RandomScheduling.java) * [RRScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/RRScheduling.java) + * [SelfAdjustingScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/SelfAdjustingScheduling.java) * [SJFScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/SJFScheduling.java) + * [SlackTimeScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/SlackTimeScheduling.java) * [SRTFScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/SRTFScheduling.java) * searches * [BinarySearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/BinarySearch.java) * [BinarySearch2dArray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/BinarySearch2dArray.java) + * [BM25InvertedIndex](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/BM25InvertedIndex.java) * [BreadthFirstSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/BreadthFirstSearch.java) * [DepthFirstSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/DepthFirstSearch.java) * [ExponentalSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/ExponentalSearch.java) @@ -494,6 +603,7 @@ * [PerfectBinarySearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/PerfectBinarySearch.java) * [QuickSelect](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/QuickSelect.java) * [RabinKarpAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/RabinKarpAlgorithm.java) + * [RandomSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/RandomSearch.java) * [RecursiveBinarySearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/RecursiveBinarySearch.java) * [RowColumnWiseSorted2dArrayBinarySearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/RowColumnWiseSorted2dArrayBinarySearch.java) * [SaddlebackSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/SaddlebackSearch.java) @@ -503,7 +613,13 @@ * [TernarySearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/TernarySearch.java) * [UnionFind](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/UnionFind.java) * [UpperBound](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/UpperBound.java) + * slidingwindow + * [LongestSubarrayWithSumLessOrEqualToK](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/slidingwindow/LongestSubarrayWithSumLessOrEqualToK.java) + * [LongestSubstringWithoutRepeatingCharacters](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharacters.java) + * [MaxSumKSizeSubarray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarray.java) + * [MinSumKSizeSubarray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/slidingwindow/MinSumKSizeSubarray.java) * sorts + * [AdaptiveMergeSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/AdaptiveMergeSort.java) * [BeadSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/BeadSort.java) * [BinaryInsertionSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/BinaryInsertionSort.java) * [BitonicSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/BitonicSort.java) @@ -516,6 +632,7 @@ * [CombSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/CombSort.java) * [CountingSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/CountingSort.java) * [CycleSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/CycleSort.java) + * [DarkSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/DarkSort.java) * [DualPivotQuickSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/DualPivotQuickSort.java) * [DutchNationalFlagSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/DutchNationalFlagSort.java) * [ExchangeSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/ExchangeSort.java) @@ -543,6 +660,7 @@ * [SortUtils](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SortUtils.java) * [SortUtilsRandomGenerator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SortUtilsRandomGenerator.java) * [SpreadSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SpreadSort.java) + * [StalinSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/StalinSort.java) * [StoogeSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/StoogeSort.java) * [StrandSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/StrandSort.java) * [SwapSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SwapSort.java) @@ -553,17 +671,27 @@ * [WiggleSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/WiggleSort.java) * stacks * [BalancedBrackets](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/BalancedBrackets.java) + * [CelebrityFinder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/CelebrityFinder.java) * [DecimalToAnyUsingStack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/DecimalToAnyUsingStack.java) * [DuplicateBrackets](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/DuplicateBrackets.java) + * [GreatestElementConstantTime](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/GreatestElementConstantTime.java) * [InfixToPostfix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/InfixToPostfix.java) * [InfixToPrefix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/InfixToPrefix.java) * [LargestRectangle](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/LargestRectangle.java) * [MaximumMinimumWindow](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/MaximumMinimumWindow.java) + * [MinStackUsingSingleStack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/MinStackUsingSingleStack.java) + * [MinStackUsingTwoStacks](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/MinStackUsingTwoStacks.java) * [NextGreaterElement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/NextGreaterElement.java) * [NextSmallerElement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/NextSmallerElement.java) + * [PalindromeWithStack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java) + * [PostfixEvaluator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/PostfixEvaluator.java) * [PostfixToInfix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/PostfixToInfix.java) + * [PrefixEvaluator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/PrefixEvaluator.java) * [PrefixToInfix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/PrefixToInfix.java) + * [SmallestElementConstantTime](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/SmallestElementConstantTime.java) + * [SortStack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/SortStack.java) * [StackPostfixNotation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/StackPostfixNotation.java) + * [StackUsingTwoQueues](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/StackUsingTwoQueues.java) * strings * [AhoCorasick](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/AhoCorasick.java) * [Alphabetical](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/Alphabetical.java) @@ -578,6 +706,7 @@ * [Isomorphic](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/Isomorphic.java) * [KMP](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/KMP.java) * [LetterCombinationsOfPhoneNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/LetterCombinationsOfPhoneNumber.java) + * [LongestCommonPrefix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/LongestCommonPrefix.java) * [LongestNonRepetitiveSubstring](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java) * [LongestPalindromicSubstring](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/LongestPalindromicSubstring.java) * [Lower](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/Lower.java) @@ -599,16 +728,20 @@ * [WordLadder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/WordLadder.java) * zigZagPattern * [ZigZagPattern](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/zigZagPattern/ZigZagPattern.java) + * tree + * [HeavyLightDecomposition](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java) * test * java * com * thealgorithms * audiofilters + * [EMAFilterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/audiofilters/EMAFilterTest.java) * [IIRFilterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/audiofilters/IIRFilterTest.java) * backtracking * [AllPathsFromSourceToTargetTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/AllPathsFromSourceToTargetTest.java) * [ArrayCombinationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/ArrayCombinationTest.java) * [CombinationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/CombinationTest.java) + * [CrosswordSolverTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/CrosswordSolverTest.java) * [FloodFillTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/FloodFillTest.java) * [KnightsTourTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/KnightsTourTest.java) * [MazeRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java) @@ -618,34 +751,63 @@ * [PermutationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/PermutationTest.java) * [PowerSumTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/PowerSumTest.java) * [SubsequenceFinderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/SubsequenceFinderTest.java) + * [WordPatternMatcherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/WordPatternMatcherTest.java) * [WordSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/WordSearchTest.java) * bitmanipulation + * [BcdConversionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/BcdConversionTest.java) + * [BinaryPalindromeCheckTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheckTest.java) * [BitSwapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/BitSwapTest.java) + * [BooleanAlgebraGatesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/BooleanAlgebraGatesTest.java) + * [ClearLeftmostSetBitTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/ClearLeftmostSetBitTest.java) + * [CountLeadingZerosTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/CountLeadingZerosTest.java) * [CountSetBitsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java) + * [FindNthBitTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/FindNthBitTest.java) + * [FirstDifferentBitTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/FirstDifferentBitTest.java) + * [GenerateSubsetsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/GenerateSubsetsTest.java) + * [GrayCodeConversionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/GrayCodeConversionTest.java) + * [HammingDistanceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/HammingDistanceTest.java) + * [HigherLowerPowerOfTwoTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/HigherLowerPowerOfTwoTest.java) * [HighestSetBitTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/HighestSetBitTest.java) * [IndexOfRightMostSetBitTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/IndexOfRightMostSetBitTest.java) * [IsEvenTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/IsEvenTest.java) * [IsPowerTwoTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/IsPowerTwoTest.java) * [LowestSetBitTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/LowestSetBitTest.java) + * [ModuloPowerOfTwoTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/ModuloPowerOfTwoTest.java) + * [NextHigherSameBitCountTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/NextHigherSameBitCountTest.java) * [NonRepeatingNumberFinderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinderTest.java) + * [NumberAppearingOddTimesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/NumberAppearingOddTimesTest.java) * [NumbersDifferentSignsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/NumbersDifferentSignsTest.java) + * [OneBitDifferenceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/OneBitDifferenceTest.java) + * [OnesComplementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/OnesComplementTest.java) + * [ParityCheckTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/ParityCheckTest.java) * [ReverseBitsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/ReverseBitsTest.java) * [SingleBitOperationsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/SingleBitOperationsTest.java) + * [SingleElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/SingleElementTest.java) + * [SwapAdjacentBitsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/SwapAdjacentBitsTest.java) + * [TwosComplementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/TwosComplementTest.java) + * [Xs3ConversionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/Xs3ConversionTest.java) * ciphers * a5 * [A5CipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/a5/A5CipherTest.java) * [A5KeyStreamGeneratorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/a5/A5KeyStreamGeneratorTest.java) * [LFSRTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/a5/LFSRTest.java) + * [ADFGVXCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/ADFGVXCipherTest.java) * [AESEncryptionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/AESEncryptionTest.java) * [AffineCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/AffineCipherTest.java) + * [AtbashTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/AtbashTest.java) * [AutokeyTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/AutokeyTest.java) + * [BaconianCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/BaconianCipherTest.java) * [BlowfishTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/BlowfishTest.java) * [CaesarTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/CaesarTest.java) * [ColumnarTranspositionCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/ColumnarTranspositionCipherTest.java) * [DESTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/DESTest.java) + * [DiffieHellmanTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/DiffieHellmanTest.java) + * [ECCTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/ECCTest.java) * [HillCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/HillCipherTest.java) + * [MonoAlphabeticTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/MonoAlphabeticTest.java) * [PlayfairTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/PlayfairTest.java) * [PolybiusTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/PolybiusTest.java) + * [RailFenceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/RailFenceTest.java) * [RSATest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/RSATest.java) * [SimpleSubCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/SimpleSubCipherTest.java) * [VigenereTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/VigenereTest.java) @@ -653,6 +815,7 @@ * conversions * [AffineConverterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/AffineConverterTest.java) * [AnyBaseToDecimalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/AnyBaseToDecimalTest.java) + * [AnytoAnyTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/AnytoAnyTest.java) * [BinaryToDecimalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/BinaryToDecimalTest.java) * [BinaryToHexadecimalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/BinaryToHexadecimalTest.java) * [BinaryToOctalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/BinaryToOctalTest.java) @@ -660,17 +823,25 @@ * [DecimalToBinaryTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/DecimalToBinaryTest.java) * [DecimalToHexadecimalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/DecimalToHexadecimalTest.java) * [DecimalToOctalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/DecimalToOctalTest.java) + * [EndianConverterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/EndianConverterTest.java) * [HexaDecimalToBinaryTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/HexaDecimalToBinaryTest.java) * [HexaDecimalToDecimalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/HexaDecimalToDecimalTest.java) * [HexToOctTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/HexToOctTest.java) * [IntegerToEnglishTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/IntegerToEnglishTest.java) * [IntegerToRomanTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/IntegerToRomanTest.java) + * [IPConverterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/IPConverterTest.java) + * [IPv6ConverterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/IPv6ConverterTest.java) + * [MorseCodeConverterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/MorseCodeConverterTest.java) + * [NumberToWordsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/NumberToWordsTest.java) * [OctalToBinaryTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/OctalToBinaryTest.java) * [OctalToDecimalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/OctalToDecimalTest.java) * [OctalToHexadecimalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/OctalToHexadecimalTest.java) + * [PhoneticAlphabetConverterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/PhoneticAlphabetConverterTest.java) * [RomanToIntegerTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/RomanToIntegerTest.java) + * [TurkishToLatinConversionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/TurkishToLatinConversionTest.java) * [UnitConversionsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/UnitConversionsTest.java) * [UnitsConverterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/UnitsConverterTest.java) + * [WordsToNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/WordsToNumberTest.java) * datastructures * bag * [BagTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java) @@ -694,32 +865,56 @@ * dynamicarray * [DynamicArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java) * graphs + * [AStarTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java) * [BipartiteGraphDFSTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFSTest.java) * [BoruvkaAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/BoruvkaAlgorithmTest.java) * [DijkstraAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithmTest.java) + * [DijkstraOptimizedAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithmTest.java) * [EdmondsBlossomAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithmTest.java) + * [FloydWarshallTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/FloydWarshallTest.java) * [FordFulkersonTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/FordFulkersonTest.java) * [HamiltonianCycleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/HamiltonianCycleTest.java) + * [JohnsonsAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithmTest.java) + * [KahnsAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithmTest.java) * [KosarajuTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/KosarajuTest.java) + * [KruskalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/KruskalTest.java) + * [MatrixGraphsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/MatrixGraphsTest.java) + * [PrimMSTTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/PrimMSTTest.java) * [TarjansAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithmTest.java) * [WelshPowellTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/WelshPowellTest.java) * hashmap * hashing * [GenericHashMapUsingArrayListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayListTest.java) * [GenericHashMapUsingArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java) + * [HashMapCuckooHashingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashingTest.java) * [HashMapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java) + * [IntersectionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/IntersectionTest.java) * [LinearProbingHashMapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMapTest.java) * [MajorityElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElementTest.java) * [MapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MapTest.java) - * [HashMapCuckooHashingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/HashMapCuckooHashingTest.java) * heaps * [FibonacciHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/FibonacciHeapTest.java) + * [GenericHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/GenericHeapTest.java) + * [HeapElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/HeapElementTest.java) + * [KthElementFinderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/KthElementFinderTest.java) * [LeftistHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/LeftistHeapTest.java) + * [MaxHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/MaxHeapTest.java) + * [MedianFinderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/MedianFinderTest.java) + * [MergeKSortedArraysTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/MergeKSortedArraysTest.java) + * [MinHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/MinHeapTest.java) + * [MinPriorityQueueTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/MinPriorityQueueTest.java) * lists * [CircleLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CircleLinkedListTest.java) + * [CountSinglyLinkedListRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java) + * [CreateAndDetectLoopTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoopTest.java) + * [CursorLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CursorLinkedListTest.java) + * [MergeKSortedLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedListTest.java) + * [MergeSortedArrayListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java) + * [MergeSortedSinglyLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedListTest.java) * [QuickSortLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/QuickSortLinkedListTest.java) * [ReverseKGroupTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/ReverseKGroupTest.java) * [RotateSinglyLinkedListsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedListsTest.java) + * [SearchSinglyLinkedListRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/SearchSinglyLinkedListRecursionTest.java) * [SinglyLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/SinglyLinkedListTest.java) * [SkipListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/SkipListTest.java) * [SortedLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/SortedLinkedListTest.java) @@ -729,13 +924,20 @@ * [GenericArrayListQueueTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/queues/GenericArrayListQueueTest.java) * [LinkedQueueTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/queues/LinkedQueueTest.java) * [PriorityQueuesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/queues/PriorityQueuesTest.java) + * [QueueByTwoStacksTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/queues/QueueByTwoStacksTest.java) * [QueueTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/queues/QueueTest.java) + * [SlidingWindowMaximumTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/queues/SlidingWindowMaximumTest.java) + * [TokenBucketTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/queues/TokenBucketTest.java) * stacks - * [LinkedListStackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/stacks/LinkedListStackTest.java) + * [NodeStackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/stacks/NodeStackTest.java) + * [ReverseStackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/stacks/ReverseStackTest.java) * [StackArrayListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/stacks/StackArrayListTest.java) * [StackArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/stacks/StackArrayTest.java) + * [StackOfLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/stacks/StackOfLinkedListTest.java) * trees + * [AVLTreeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/AVLTreeTest.java) * [BinaryTreeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeTest.java) + * [BoundaryTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/BoundaryTraversalTest.java) * [BSTFromSortedArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/BSTFromSortedArrayTest.java) * [BSTIterativeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/BSTIterativeTest.java) * [BSTRecursiveTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/BSTRecursiveTest.java) @@ -750,24 +952,38 @@ * [LevelOrderTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/LevelOrderTraversalTest.java) * [PostOrderTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/PostOrderTraversalTest.java) * [PreOrderTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/PreOrderTraversalTest.java) + * [QuadTreeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/QuadTreeTest.java) * [SameTreesCheckTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/SameTreesCheckTest.java) * [SplayTreeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/SplayTreeTest.java) * [TreapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/TreapTest.java) * [TreeTestUtils](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/TreeTestUtils.java) - * [TrieImpTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/TrieImpTest.java) + * [TrieTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/TrieTest.java) * [VerticalOrderTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/VerticalOrderTraversalTest.java) * [ZigzagTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/ZigzagTraversalTest.java) * divideandconquer * [BinaryExponentiationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/BinaryExponentiationTest.java) * [ClosestPairTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java) + * [CountingInversionsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/CountingInversionsTest.java) + * [MedianOfTwoSortedArraysTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/MedianOfTwoSortedArraysTest.java) * [SkylineAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/SkylineAlgorithmTest.java) * [StrassenMatrixMultiplicationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/StrassenMatrixMultiplicationTest.java) + * [TilingProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/TilingProblemTest.java) * dynamicprogramming + * [AbbreviationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/AbbreviationTest.java) + * [AllConstructTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/AllConstructTest.java) + * [AssignmentUsingBitmaskTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmaskTest.java) * [BoardPathTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/BoardPathTest.java) + * [BoundaryFillTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/BoundaryFillTest.java) + * [BruteForceKnapsackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsackTest.java) * [CatalanNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/CatalanNumberTest.java) * [ClimbStairsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/ClimbStairsTest.java) + * [CoinChangeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/CoinChangeTest.java) + * [CountFriendsPairingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/CountFriendsPairingTest.java) + * [DPTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/DPTest.java) * [EditDistanceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/EditDistanceTest.java) * [EggDroppingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/EggDroppingTest.java) + * [FibonacciTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/FibonacciTest.java) + * [KadaneAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/KadaneAlgorithmTest.java) * [KnapsackMemoizationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackMemoizationTest.java) * [KnapsackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackTest.java) * [LevenshteinDistanceTests](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/LevenshteinDistanceTests.java) @@ -777,31 +993,53 @@ * [LongestIncreasingSubsequenceTests](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceTests.java) * [LongestPalindromicSubstringTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubstringTest.java) * [LongestValidParenthesesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/LongestValidParenthesesTest.java) + * [MatrixChainMultiplicationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/MatrixChainMultiplicationTest.java) + * [MatrixChainRecursiveTopDownMemoisationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/MatrixChainRecursiveTopDownMemoisationTest.java) + * [MaximumSumOfNonAdjacentElementsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/MaximumSumOfNonAdjacentElementsTest.java) * [MinimumPathSumTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/MinimumPathSumTest.java) * [MinimumSumPartitionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/MinimumSumPartitionTest.java) + * [NewManShanksPrimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/NewManShanksPrimeTest.java) * [OptimalJobSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/OptimalJobSchedulingTest.java) * [PalindromicPartitioningTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/PalindromicPartitioningTest.java) * [PartitionProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/PartitionProblemTest.java) * [RegexMatchingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/RegexMatchingTest.java) + * [RodCuttingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/RodCuttingTest.java) * [ShortestCommonSupersequenceLengthTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/ShortestCommonSupersequenceLengthTest.java) * [SubsetCountTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/SubsetCountTest.java) + * [SubsetSumSpaceOptimizedTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimizedTest.java) * [SubsetSumTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/SubsetSumTest.java) * [SumOfSubsetTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/SumOfSubsetTest.java) + * [TreeMatchingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/TreeMatchingTest.java) * [TribonacciTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/TribonacciTest.java) * [UniquePathsTests](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/UniquePathsTests.java) + * [UniqueSubsequencesCountTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCountTest.java) * [WildcardMatchingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/WildcardMatchingTest.java) + * [WineProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java) * geometry + * [BresenhamLineTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/BresenhamLineTest.java) + * [ConvexHullTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/ConvexHullTest.java) * [GrahamScanTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/GrahamScanTest.java) + * [MidpointCircleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/MidpointCircleTest.java) + * [MidpointEllipseTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/MidpointEllipseTest.java) + * graph + * [ConstrainedShortestPathTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/graph/ConstrainedShortestPathTest.java) + * [StronglyConnectedComponentOptimizedTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/graph/StronglyConnectedComponentOptimizedTest.java) * greedyalgorithms * [ActivitySelectionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/ActivitySelectionTest.java) + * [BandwidthAllocationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/BandwidthAllocationTest.java) * [BinaryAdditionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/BinaryAdditionTest.java) * [CoinChangeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/CoinChangeTest.java) * [DigitSeparationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/DigitSeparationTest.java) + * [EgyptianFractionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/EgyptianFractionTest.java) * [FractionalKnapsackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/FractionalKnapsackTest.java) * [GaleShapleyTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/GaleShapleyTest.java) * [JobSequencingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/JobSequencingTest.java) + * [KCentersTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/KCentersTest.java) * [MergeIntervalsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/MergeIntervalsTest.java) * [MinimizingLatenessTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/MinimizingLatenessTest.java) + * [MinimumWaitingTimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTimeTest.java) + * [OptimalFileMergingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/OptimalFileMergingTest.java) + * [StockProfitCalculatorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/StockProfitCalculatorTest.java) * io * [BufferedReaderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/io/BufferedReaderTest.java) * lineclipping @@ -821,17 +1059,23 @@ * [AverageTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/AverageTest.java) * [BinaryPowTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/BinaryPowTest.java) * [BinomialCoefficientTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/BinomialCoefficientTest.java) + * [CatalanNumbersTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/CatalanNumbersTest.java) * [CeilTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/CeilTest.java) + * [ChineseRemainderTheoremTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/ChineseRemainderTheoremTest.java) * [CollatzConjectureTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/CollatzConjectureTest.java) * [CombinationsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/CombinationsTest.java) + * [ConvolutionFFTTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/ConvolutionFFTTest.java) + * [ConvolutionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/ConvolutionTest.java) * [CrossCorrelationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/CrossCorrelationTest.java) * [DeterminantOfMatrixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/DeterminantOfMatrixTest.java) * [DigitalRootTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/DigitalRootTest.java) * [DistanceFormulaTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/DistanceFormulaTest.java) * [DudeneyNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/DudeneyNumberTest.java) + * [EulerMethodTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/EulerMethodTest.java) * [EulersFunctionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/EulersFunctionTest.java) * [FactorialRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FactorialRecursionTest.java) * [FactorialTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FactorialTest.java) + * [FastExponentiationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FastExponentiationTest.java) * [FastInverseSqrtTests](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FastInverseSqrtTests.java) * [FFTTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FFTTest.java) * [FibonacciJavaStreamsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FibonacciJavaStreamsTest.java) @@ -849,23 +1093,22 @@ * [GCDRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/GCDRecursionTest.java) * [GCDTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/GCDTest.java) * [GenericRootTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/GenericRootTest.java) + * [GoldbachConjectureTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/GoldbachConjectureTest.java) * [HarshadNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/HarshadNumberTest.java) * [HeronsFormulaTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/HeronsFormulaTest.java) * [JosephusProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/JosephusProblemTest.java) * [KaprekarNumbersTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/KaprekarNumbersTest.java) + * [KaratsubaMultiplicationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/KaratsubaMultiplicationTest.java) + * [KrishnamurthyNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/KrishnamurthyNumberTest.java) * [LeastCommonMultipleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/LeastCommonMultipleTest.java) * [LeonardoNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/LeonardoNumberTest.java) - * [LiouvilleLambdaFunctionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/LiouvilleLambdaFunctionTest.java) * [LongDivisionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/LongDivisionTest.java) * [LucasSeriesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/LucasSeriesTest.java) - * [MatrixRankTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/MatrixRankTest.java) - * [MatrixUtilTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/MatrixUtilTest.java) + * [MathBuilderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/MathBuilderTest.java) * [MaxValueTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/MaxValueTest.java) * [MeansTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/MeansTest.java) * [MedianTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/MedianTest.java) - * [MillerRabinPrimalityCheckTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/MillerRabinPrimalityCheckTest.java) * [MinValueTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/MinValueTest.java) - * [MobiusFunctionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/MobiusFunctionTest.java) * [ModeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/ModeTest.java) * [NonRepeatingElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/NonRepeatingElementTest.java) * [NthUglyNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/NthUglyNumberTest.java) @@ -881,13 +1124,19 @@ * [PowerOfTwoOrNotTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PowerOfTwoOrNotTest.java) * [PowerUsingRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PowerUsingRecursionTest.java) * [PowTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PowTest.java) - * [PrimeCheckTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PrimeCheckTest.java) - * [PrimeFactorizationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PrimeFactorizationTest.java) + * prime + * [LiouvilleLambdaFunctionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/prime/LiouvilleLambdaFunctionTest.java) + * [MillerRabinPrimalityCheckTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/prime/MillerRabinPrimalityCheckTest.java) + * [MobiusFunctionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/prime/MobiusFunctionTest.java) + * [PrimeCheckTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/prime/PrimeCheckTest.java) + * [PrimeFactorizationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/prime/PrimeFactorizationTest.java) * [PronicNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PronicNumberTest.java) * [PythagoreanTripleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PythagoreanTripleTest.java) + * [QuadraticEquationSolverTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/QuadraticEquationSolverTest.java) * [ReverseNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/ReverseNumberTest.java) * [SecondMinMaxTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SecondMinMaxTest.java) * [SieveOfEratosthenesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SieveOfEratosthenesTest.java) + * [SolovayStrassenPrimalityTestTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SolovayStrassenPrimalityTestTest.java) * [SquareFreeIntegerTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SquareFreeIntegerTest.java) * [SquareRootwithBabylonianMethodTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SquareRootwithBabylonianMethodTest.java) * [SquareRootWithNewtonRaphsonTestMethod](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SquareRootWithNewtonRaphsonTestMethod.java) @@ -896,18 +1145,31 @@ * [StrobogrammaticNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/StrobogrammaticNumberTest.java) * [SumOfArithmeticSeriesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SumOfArithmeticSeriesTest.java) * [SumOfDigitsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SumOfDigitsTest.java) + * [SumOfOddNumbersTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SumOfOddNumbersTest.java) * [SumWithoutArithmeticOperatorsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SumWithoutArithmeticOperatorsTest.java) * [TestArmstrong](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/TestArmstrong.java) * [TwinPrimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/TwinPrimeTest.java) + * [UniformNumbersTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/UniformNumbersTest.java) + * [VampireNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/VampireNumberTest.java) * [VolumeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/VolumeTest.java) + * matrix + * [InverseOfMatrixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/matrix/InverseOfMatrixTest.java) + * [MatrixRankTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/matrix/MatrixRankTest.java) + * [MatrixTransposeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/matrix/MatrixTransposeTest.java) + * [MatrixUtilTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/matrix/MatrixUtilTest.java) + * [MedianOfMatrixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/matrix/MedianOfMatrixTest.java) + * [MirrorOfMatrixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/matrix/MirrorOfMatrixTest.java) + * [TestPrintMatrixInSpiralOrder](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/matrix/TestPrintMatrixInSpiralOrder.java) * misc * [ColorContrastRatioTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/ColorContrastRatioTest.java) - * [InverseOfMatrixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/InverseOfMatrixTest.java) * [MapReduceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/MapReduceTest.java) - * [MedianOfMatrixtest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/MedianOfMatrixtest.java) * [MedianOfRunningArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/MedianOfRunningArrayTest.java) - * [MirrorOfMatrixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/MirrorOfMatrixTest.java) + * [PalindromePrimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java) * [PalindromeSinglyLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java) + * [RangeInSortedArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java) + * [ShuffleArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/ShuffleArrayTest.java) + * [SparsityTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/SparsityTest.java) + * [ThreeSumProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/ThreeSumProblemTest.java) * [TwoSumProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/TwoSumProblemTest.java) * others * [ArrayLeftRotationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/ArrayLeftRotationTest.java) @@ -927,6 +1189,7 @@ * [LineSweepTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/LineSweepTest.java) * [LinkListSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/LinkListSortTest.java) * [LowestBasePalindromeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/LowestBasePalindromeTest.java) + * [MaximumSlidingWindowTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/MaximumSlidingWindowTest.java) * [MaximumSumOfDistinctSubarraysWithLengthKTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/MaximumSumOfDistinctSubarraysWithLengthKTest.java) * [NewManShanksPrimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/NewManShanksPrimeTest.java) * [NextFitTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/NextFitTest.java) @@ -938,31 +1201,79 @@ * [TestPrintMatrixInSpiralOrder](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/TestPrintMatrixInSpiralOrder.java) * [TwoPointersTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/TwoPointersTest.java) * [WorstFitCPUTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/WorstFitCPUTest.java) - * Recursion - * [GenerateSubsetsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/Recursion/GenerateSubsetsTest.java) + * puzzlesandgames + * [SudokuTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/puzzlesandgames/SudokuTest.java) + * [TowerOfHanoiTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/puzzlesandgames/TowerOfHanoiTest.java) + * [WordBoggleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/puzzlesandgames/WordBoggleTest.java) + * recursion + * [FibonacciSeriesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/recursion/FibonacciSeriesTest.java) + * [GenerateSubsetsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/recursion/GenerateSubsetsTest.java) * scheduling + * [AgingSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/AgingSchedulingTest.java) + * diskscheduling + * [CircularLookSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/diskscheduling/CircularLookSchedulingTest.java) + * [CircularScanSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/diskscheduling/CircularScanSchedulingTest.java) + * [LookSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/diskscheduling/LookSchedulingTest.java) + * [ScanSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/diskscheduling/ScanSchedulingTest.java) + * [SSFSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/diskscheduling/SSFSchedulingTest.java) + * [EDFSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/EDFSchedulingTest.java) + * [FairShareSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/FairShareSchedulingTest.java) * [FCFSSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/FCFSSchedulingTest.java) + * [GangSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/GangSchedulingTest.java) * [HighestResponseRatioNextSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/HighestResponseRatioNextSchedulingTest.java) + * [JobSchedulingWithDeadlineTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/JobSchedulingWithDeadlineTest.java) + * [LotterySchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/LotterySchedulingTest.java) * [MLFQSchedulerTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/MLFQSchedulerTest.java) + * [MultiAgentSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/MultiAgentSchedulingTest.java) + * [NonPreemptivePrioritySchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/NonPreemptivePrioritySchedulingTest.java) * [PreemptivePrioritySchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/PreemptivePrioritySchedulingTest.java) + * [ProportionalFairSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/ProportionalFairSchedulingTest.java) + * [RandomSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/RandomSchedulingTest.java) * [RRSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/RRSchedulingTest.java) + * [SelfAdjustingSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/SelfAdjustingSchedulingTest.java) * [SJFSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/SJFSchedulingTest.java) + * [SlackTimeSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/SlackTimeSchedulingTest.java) * [SRTFSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/SRTFSchedulingTest.java) * searches * [BinarySearch2dArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/BinarySearch2dArrayTest.java) + * [BinarySearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/BinarySearchTest.java) + * [BM25InvertedIndexTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/BM25InvertedIndexTest.java) * [BreadthFirstSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/BreadthFirstSearchTest.java) * [DepthFirstSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/DepthFirstSearchTest.java) + * [ExponentialSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/ExponentialSearchTest.java) + * [FibonacciSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/FibonacciSearchTest.java) * [HowManyTimesRotatedTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/HowManyTimesRotatedTest.java) + * [InterpolationSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/InterpolationSearchTest.java) + * [IterativeBinarySearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/IterativeBinarySearchTest.java) + * [IterativeTernarySearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/IterativeTernarySearchTest.java) + * [JumpSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/JumpSearchTest.java) * [KMPSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/KMPSearchTest.java) + * [LinearSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/LinearSearchTest.java) + * [LinearSearchThreadTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/LinearSearchThreadTest.java) + * [LowerBoundTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/LowerBoundTest.java) + * [MonteCarloTreeSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/MonteCarloTreeSearchTest.java) * [OrderAgnosticBinarySearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/OrderAgnosticBinarySearchTest.java) * [PerfectBinarySearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/PerfectBinarySearchTest.java) * [QuickSelectTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/QuickSelectTest.java) * [RabinKarpAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/RabinKarpAlgorithmTest.java) + * [RandomSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/RandomSearchTest.java) * [RecursiveBinarySearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/RecursiveBinarySearchTest.java) * [RowColumnWiseSorted2dArrayBinarySearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/RowColumnWiseSorted2dArrayBinarySearchTest.java) + * [SaddlebackSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/SaddlebackSearchTest.java) + * [SearchInARowAndColWiseSortedMatrixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrixTest.java) * [SortOrderAgnosticBinarySearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearchTest.java) + * [SquareRootBinarySearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/SquareRootBinarySearchTest.java) + * [TernarySearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/TernarySearchTest.java) * [TestSearchInARowAndColWiseSortedMatrix](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/TestSearchInARowAndColWiseSortedMatrix.java) + * [UnionFindTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/UnionFindTest.java) + * [UpperBoundTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/UpperBoundTest.java) + * slidingwindow + * [LongestSubarrayWithSumLessOrEqualToKTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/slidingwindow/LongestSubarrayWithSumLessOrEqualToKTest.java) + * [LongestSubstringWithoutRepeatingCharactersTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharactersTest.java) + * [MaxSumKSizeSubarrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarrayTest.java) + * [MinSumKSizeSubarrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/slidingwindow/MinSumKSizeSubarrayTest.java) * sorts + * [AdaptiveMergeSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/AdaptiveMergeSortTest.java) * [BeadSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/BeadSortTest.java) * [BinaryInsertionSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/BinaryInsertionSortTest.java) * [BitonicSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/BitonicSortTest.java) @@ -975,6 +1286,7 @@ * [CombSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/CombSortTest.java) * [CountingSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/CountingSortTest.java) * [CycleSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/CycleSortTest.java) + * [DarkSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/DarkSortTest.java) * [DualPivotQuickSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/DualPivotQuickSortTest.java) * [DutchNationalFlagSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/DutchNationalFlagSortTest.java) * [ExchangeSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/ExchangeSortTest.java) @@ -1001,6 +1313,7 @@ * [SortUtilsRandomGeneratorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SortUtilsRandomGeneratorTest.java) * [SortUtilsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SortUtilsTest.java) * [SpreadSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SpreadSortTest.java) + * [StalinSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/StalinSortTest.java) * [StoogeSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/StoogeSortTest.java) * [StrandSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/StrandSortTest.java) * [SwapSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SwapSortTest.java) @@ -1011,16 +1324,26 @@ * [WiggleSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/WiggleSortTest.java) * stacks * [BalancedBracketsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/BalancedBracketsTest.java) + * [CelebrityFinderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/CelebrityFinderTest.java) * [DecimalToAnyUsingStackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/DecimalToAnyUsingStackTest.java) * [DuplicateBracketsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/DuplicateBracketsTest.java) + * [GreatestElementConstantTimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/GreatestElementConstantTimeTest.java) * [InfixToPostfixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/InfixToPostfixTest.java) * [InfixToPrefixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/InfixToPrefixTest.java) * [LargestRectangleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/LargestRectangleTest.java) + * [MinStackUsingSingleStackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/MinStackUsingSingleStackTest.java) + * [MinStackUsingTwoStacksTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/MinStackUsingTwoStacksTest.java) * [NextGreaterElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/NextGreaterElementTest.java) * [NextSmallerElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/NextSmallerElementTest.java) + * [PalindromeWithStackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/PalindromeWithStackTest.java) + * [PostfixEvaluatorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/PostfixEvaluatorTest.java) * [PostfixToInfixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/PostfixToInfixTest.java) + * [PrefixEvaluatorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/PrefixEvaluatorTest.java) * [PrefixToInfixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/PrefixToInfixTest.java) + * [SmallestElementConstantTimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/SmallestElementConstantTimeTest.java) + * [SortStackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/SortStackTest.java) * [StackPostfixNotationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/StackPostfixNotationTest.java) + * [StackUsingTwoQueuesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/StackUsingTwoQueuesTest.java) * strings * [AhoCorasickTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/AhoCorasickTest.java) * [AlphabeticalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/AlphabeticalTest.java) @@ -1034,6 +1357,7 @@ * [HorspoolSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/HorspoolSearchTest.java) * [IsomorphicTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/IsomorphicTest.java) * [LetterCombinationsOfPhoneNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/LetterCombinationsOfPhoneNumberTest.java) + * [LongestCommonPrefixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/LongestCommonPrefixTest.java) * [LongestNonRepetitiveSubstringTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/LongestNonRepetitiveSubstringTest.java) * [LongestPalindromicSubstringTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/LongestPalindromicSubstringTest.java) * [LowerTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/LowerTest.java) @@ -1054,3 +1378,5 @@ * [WordLadderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/WordLadderTest.java) * zigZagPattern * [ZigZagPatternTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/zigZagPattern/ZigZagPatternTest.java) + * tree + * [HeavyLightDecompositionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java) diff --git a/checkstyle.xml b/checkstyle.xml index 4fc237d29c5a..d78724455af7 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -183,7 +183,7 @@ - + diff --git a/pmd-exclude.properties b/pmd-exclude.properties index f6ee88196962..1848412c9d30 100644 --- a/pmd-exclude.properties +++ b/pmd-exclude.properties @@ -1,29 +1,19 @@ -com.thealgorithms.bitmanipulation.SingleBitOperations=UselessParentheses com.thealgorithms.ciphers.AffineCipher=UselessParentheses -com.thealgorithms.ciphers.ColumnarTranspositionCipher=UnnecessaryFullyQualifiedName com.thealgorithms.ciphers.DES=UselessParentheses -com.thealgorithms.ciphers.HillCipher=UselessParentheses com.thealgorithms.ciphers.RSA=UselessParentheses com.thealgorithms.conversions.AnyBaseToAnyBase=UselessParentheses com.thealgorithms.conversions.AnytoAny=UselessParentheses -com.thealgorithms.conversions.HexToOct=UselessParentheses -com.thealgorithms.conversions.IntegerToRoman=UnnecessaryFullyQualifiedName -com.thealgorithms.datastructures.crdt.LWWElementSet=UselessParentheses com.thealgorithms.datastructures.crdt.Pair=UnusedPrivateField com.thealgorithms.datastructures.graphs.AStar=UselessParentheses com.thealgorithms.datastructures.graphs.AdjacencyMatrixGraph=CollapsibleIfStatements,UnnecessaryFullyQualifiedName,UselessParentheses com.thealgorithms.datastructures.graphs.BipartiteGraphDFS=CollapsibleIfStatements -com.thealgorithms.datastructures.graphs.Kruskal=UselessParentheses com.thealgorithms.datastructures.hashmap.hashing.HashMapCuckooHashing=UselessParentheses com.thealgorithms.datastructures.heaps.FibonacciHeap=UselessParentheses -com.thealgorithms.datastructures.heaps.HeapElement=UselessParentheses com.thealgorithms.datastructures.heaps.HeapNode=UselessParentheses com.thealgorithms.datastructures.lists.DoublyLinkedList=UselessParentheses com.thealgorithms.datastructures.lists.SearchSinglyLinkedListRecursion=UselessParentheses com.thealgorithms.datastructures.lists.SinglyLinkedList=UnusedLocalVariable com.thealgorithms.datastructures.queues.PriorityQueue=UselessParentheses -com.thealgorithms.datastructures.stacks.NodeStack=UnnecessaryFullyQualifiedName,UnusedFormalParameter -com.thealgorithms.datastructures.stacks.StackArray=UselessParentheses com.thealgorithms.datastructures.trees.CheckBinaryTreeIsValidBST=UselessParentheses com.thealgorithms.datastructures.trees.SegmentTree=UselessParentheses com.thealgorithms.devutils.nodes.LargeTreeNode=UselessParentheses @@ -32,9 +22,6 @@ com.thealgorithms.devutils.nodes.SimpleTreeNode=UselessParentheses com.thealgorithms.devutils.nodes.TreeNode=UselessParentheses com.thealgorithms.divideandconquer.ClosestPair=UnnecessaryFullyQualifiedName,UselessParentheses com.thealgorithms.divideandconquer.Point=UselessParentheses -com.thealgorithms.dynamicprogramming.MatrixChainMultiplication=UselessParentheses -com.thealgorithms.dynamicprogramming.ShortestSuperSequence=UselessParentheses -com.thealgorithms.dynamicprogramming.UniquePaths=UnnecessarySemicolon com.thealgorithms.dynamicprogramming.WineProblem=UselessParentheses com.thealgorithms.maths.BinomialCoefficient=UselessParentheses com.thealgorithms.maths.Complex=UselessParentheses @@ -46,45 +33,32 @@ com.thealgorithms.maths.KaprekarNumbers=UselessParentheses com.thealgorithms.maths.KeithNumber=UselessParentheses com.thealgorithms.maths.LeonardoNumber=UselessParentheses com.thealgorithms.maths.LinearDiophantineEquationsSolver=UselessParentheses -com.thealgorithms.maths.MatrixUtil=UselessParentheses com.thealgorithms.maths.RomanNumeralUtil=UselessParentheses com.thealgorithms.maths.SecondMinMax=UselessParentheses com.thealgorithms.maths.SecondMinMaxTest=UnnecessaryFullyQualifiedName com.thealgorithms.maths.StandardDeviation=UselessParentheses com.thealgorithms.maths.SumOfArithmeticSeries=UselessParentheses com.thealgorithms.maths.TrinomialTriangle=UselessParentheses -com.thealgorithms.maths.VampireNumber=CollapsibleIfStatements com.thealgorithms.maths.Volume=UselessParentheses -com.thealgorithms.matrixexponentiation.Fibonacci=UnnecessaryFullyQualifiedName com.thealgorithms.misc.Sparsity=UselessParentheses -com.thealgorithms.misc.ThreeSumProblem=UselessParentheses -com.thealgorithms.misc.WordBoggle=UselessParentheses com.thealgorithms.others.CRC16=UselessParentheses com.thealgorithms.others.Damm=UnnecessaryFullyQualifiedName com.thealgorithms.others.Luhn=UnnecessaryFullyQualifiedName com.thealgorithms.others.Mandelbrot=UselessParentheses -com.thealgorithms.others.MaximumSumOfDistinctSubarraysWithLengthK=CollapsibleIfStatements com.thealgorithms.others.MiniMaxAlgorithm=UselessParentheses com.thealgorithms.others.PageRank=UselessParentheses com.thealgorithms.others.PerlinNoise=UselessParentheses com.thealgorithms.others.QueueUsingTwoStacks=UselessParentheses -com.thealgorithms.others.QueueWithStack=UselessParentheses com.thealgorithms.others.Trieac=UselessParentheses com.thealgorithms.others.Verhoeff=UnnecessaryFullyQualifiedName com.thealgorithms.searches.InterpolationSearch=UselessParentheses com.thealgorithms.searches.KMPSearch=UselessParentheses -com.thealgorithms.searches.LinearSearchThread=EmptyCatchBlock com.thealgorithms.searches.RabinKarpAlgorithm=UselessParentheses com.thealgorithms.sorts.CircleSort=EmptyControlStatement -com.thealgorithms.sorts.CombSort=UselessParentheses com.thealgorithms.sorts.DutchNationalFlagSort=UselessParentheses -com.thealgorithms.sorts.LinkListSort=EmptyControlStatement,UnusedLocalVariable com.thealgorithms.sorts.MergeSortNoExtraSpace=UselessParentheses -com.thealgorithms.sorts.PigeonholeSort=UselessParentheses com.thealgorithms.sorts.RadixSort=UselessParentheses com.thealgorithms.sorts.WiggleSort=UselessParentheses com.thealgorithms.stacks.PostfixToInfix=UselessParentheses com.thealgorithms.strings.HorspoolSearch=UnnecessaryFullyQualifiedName,UselessParentheses -com.thealgorithms.strings.MyAtoi=UselessParentheses com.thealgorithms.strings.Palindrome=UselessParentheses -com.thealgorithms.strings.Solution=CollapsibleIfStatements diff --git a/pom.xml b/pom.xml index ab12e8ec4082..415a0ae0cfd5 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ UTF-8 21 21 - 3.26.3 + 3.27.3 @@ -20,7 +20,7 @@ org.junit junit-bom - 5.11.2 + 5.12.2 pom import @@ -31,7 +31,6 @@ org.junit.jupiter junit-jupiter - 5.11.2 test @@ -40,11 +39,10 @@ ${assertj.version} test - - org.junit.jupiter - junit-jupiter-api - 5.11.2 + org.mockito + mockito-core + 5.18.0 test @@ -55,7 +53,7 @@ org.apache.commons commons-collections4 - 4.5.0-M2 + 4.5.0 @@ -63,7 +61,7 @@ maven-surefire-plugin - 3.5.0 + 3.5.3 @@ -71,7 +69,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.13.0 + 3.14.0 21 21 @@ -80,7 +78,6 @@ -Xlint:-auxiliaryclass -Xlint:-rawtypes -Xlint:-unchecked - -Xlint:-lossy-conversions -Werror @@ -88,7 +85,7 @@ org.jacoco jacoco-maven-plugin - 0.8.12 + 0.8.13 @@ -107,7 +104,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 3.5.0 + 3.6.0 checkstyle.xml true @@ -118,14 +115,14 @@ com.puppycrawl.tools checkstyle - 10.18.2 + 10.24.0 com.github.spotbugs spotbugs-maven-plugin - 4.8.6.4 + 4.9.3.0 spotbugs-exclude.xml true @@ -133,12 +130,12 @@ com.mebigfatguy.fb-contrib fb-contrib - 7.6.5 + 7.6.9 com.h3xstream.findsecbugs findsecbugs-plugin - 1.13.0 + 1.14.0 @@ -146,7 +143,7 @@ org.apache.maven.plugins maven-pmd-plugin - 3.25.0 + 3.26.0 true true diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index bfc7716730c3..3b77ced1a13e 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -41,9 +41,6 @@ - - - @@ -86,10 +83,10 @@ - - + + @@ -120,9 +117,6 @@ - - - diff --git a/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java b/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java new file mode 100644 index 000000000000..0dd23e937953 --- /dev/null +++ b/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java @@ -0,0 +1,48 @@ +package com.thealgorithms.audiofilters; + +/** + * Exponential Moving Average (EMA) Filter for smoothing audio signals. + * + *

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 + * Wikipedia link. + */ +public class EMAFilter { + private final double alpha; + private double emaValue; + /** + * Constructs an EMA filter with a given smoothing factor. + * + * @param alpha Smoothing factor (0 < alpha <= 1) + * @throws IllegalArgumentException if alpha is not in (0, 1] + */ + public EMAFilter(double alpha) { + if (alpha <= 0 || alpha > 1) { + throw new IllegalArgumentException("Alpha must be between 0 and 1."); + } + this.alpha = alpha; + this.emaValue = 0.0; + } + /** + * Applies the EMA filter to an audio signal array. + * + * @param audioSignal Array of audio samples to process + * @return Array of processed (smoothed) samples + */ + public double[] apply(double[] audioSignal) { + if (audioSignal.length == 0) { + return new double[0]; + } + double[] emaSignal = new double[audioSignal.length]; + emaValue = audioSignal[0]; + emaSignal[0] = emaValue; + for (int i = 1; i < audioSignal.length; i++) { + emaValue = alpha * audioSignal[i] + (1 - alpha) * emaValue; + emaSignal[i] = emaValue; + } + return emaSignal; + } +} diff --git a/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java b/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java index a064decc0eb7..f8cd0c40c20e 100644 --- a/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java +++ b/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java @@ -4,22 +4,24 @@ import java.util.List; /** - * Finds all combinations of 0...n-1 of length k + * This class provides methods to find all combinations of integers from 0 to n-1 + * of a specified length k using backtracking. */ public final class ArrayCombination { private ArrayCombination() { } /** - * Finds all combinations of length k of 0..n-1 using backtracking. + * Generates all possible combinations of length k from the integers 0 to n-1. * - * @param n Number of the elements. - * @param k Length of the combination. - * @return A list of all combinations of length k. + * @param n The total number of elements (0 to n-1). + * @param k The desired length of each combination. + * @return A list containing all combinations of length k. + * @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("Wrong input."); + throw new IllegalArgumentException("Invalid input: n must be non-negative, k must be non-negative and less than or equal to n."); } List> combinations = new ArrayList<>(); @@ -27,16 +29,26 @@ public static List> combination(int n, int k) { return combinations; } + /** + * A helper method that uses backtracking to find combinations. + * + * @param combinations The list to store all valid combinations found. + * @param current The current combination being built. + * @param start The starting index for the current recursion. + * @param n The total number of elements (0 to n-1). + * @param k The desired length of each combination. + */ private static void combine(List> combinations, List current, int start, int n, int k) { - if (current.size() == k) { // Base case: combination found - combinations.add(new ArrayList<>(current)); // Copy to avoid modification + // Base case: combination found + if (current.size() == k) { + combinations.add(new ArrayList<>(current)); return; } for (int i = start; i < n; i++) { current.add(i); combine(combinations, current, i + 1, n, k); - current.removeLast(); // Backtrack + current.remove(current.size() - 1); // Backtrack } } } diff --git a/src/main/java/com/thealgorithms/backtracking/Combination.java b/src/main/java/com/thealgorithms/backtracking/Combination.java index bf2a672a0ef8..ecaf7428f986 100644 --- a/src/main/java/com/thealgorithms/backtracking/Combination.java +++ b/src/main/java/com/thealgorithms/backtracking/Combination.java @@ -1,6 +1,7 @@ package com.thealgorithms.backtracking; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.TreeSet; @@ -13,8 +14,6 @@ public final class Combination { private Combination() { } - private static int length; - /** * Find all combinations of given array using backtracking * @param arr the array. @@ -23,39 +22,45 @@ private Combination() { * @return a list of all combinations of length n. If n == 0, return null. */ public static List> combination(T[] arr, int n) { + if (n < 0) { + throw new IllegalArgumentException("The combination length cannot be negative."); + } + if (n == 0) { - return null; + return Collections.emptyList(); } - length = n; T[] array = arr.clone(); Arrays.sort(array); + List> result = new LinkedList<>(); - backtracking(array, 0, new TreeSet(), result); + backtracking(array, n, 0, new TreeSet(), result); return result; } /** * Backtrack all possible combinations of a given array * @param arr the array. + * @param n length of the combination * @param index the starting index. * @param currSet set that tracks current combination * @param result the list contains all combination. * @param the type of elements in the array. */ - private static void backtracking(T[] arr, int index, TreeSet currSet, List> result) { - if (index + length - currSet.size() > arr.length) { + private static void backtracking(T[] arr, int n, int index, TreeSet currSet, List> result) { + if (index + n - currSet.size() > arr.length) { return; } - if (length - 1 == currSet.size()) { + if (currSet.size() == n - 1) { for (int i = index; i < arr.length; i++) { currSet.add(arr[i]); - result.add((TreeSet) currSet.clone()); + result.add(new TreeSet<>(currSet)); currSet.remove(arr[i]); } + return; } for (int i = index; i < arr.length; i++) { currSet.add(arr[i]); - backtracking(arr, i + 1, currSet, result); + backtracking(arr, n, i + 1, currSet, result); currSet.remove(arr[i]); } } diff --git a/src/main/java/com/thealgorithms/backtracking/CrosswordSolver.java b/src/main/java/com/thealgorithms/backtracking/CrosswordSolver.java new file mode 100644 index 000000000000..6bfb026c7de9 --- /dev/null +++ b/src/main/java/com/thealgorithms/backtracking/CrosswordSolver.java @@ -0,0 +1,125 @@ +package com.thealgorithms.backtracking; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * A class to solve a crossword puzzle using backtracking. + * Example: + * Input: + * puzzle = { + * {' ', ' ', ' '}, + * {' ', ' ', ' '}, + * {' ', ' ', ' '} + * } + * words = List.of("cat", "dog") + * + * Output: + * { + * {'c', 'a', 't'}, + * {' ', ' ', ' '}, + * {'d', 'o', 'g'} + * } + */ +public final class CrosswordSolver { + private CrosswordSolver() { + } + + /** + * Checks if a word can be placed at the specified position in the crossword. + * + * @param puzzle The crossword puzzle represented as a 2D char array. + * @param word The word to be placed. + * @param row The row index where the word might be placed. + * @param col The column index where the word might be placed. + * @param vertical If true, the word is placed vertically; otherwise, horizontally. + * @return true if the word can be placed, false otherwise. + */ + public static boolean isValid(char[][] puzzle, String word, int row, int col, boolean vertical) { + for (int i = 0; i < word.length(); i++) { + if (vertical) { + if (row + i >= puzzle.length || puzzle[row + i][col] != ' ') { + return false; + } + } else { + if (col + i >= puzzle[0].length || puzzle[row][col + i] != ' ') { + return false; + } + } + } + return true; + } + + /** + * Places a word at the specified position in the crossword. + * + * @param puzzle The crossword puzzle represented as a 2D char array. + * @param word The word to be placed. + * @param row The row index where the word will be placed. + * @param col The column index where the word will be placed. + * @param vertical If true, the word is placed vertically; otherwise, horizontally. + */ + public static void placeWord(char[][] puzzle, String word, int row, int col, boolean vertical) { + for (int i = 0; i < word.length(); i++) { + if (vertical) { + puzzle[row + i][col] = word.charAt(i); + } else { + puzzle[row][col + i] = word.charAt(i); + } + } + } + + /** + * Removes a word from the specified position in the crossword. + * + * @param puzzle The crossword puzzle represented as a 2D char array. + * @param word The word to be removed. + * @param row The row index where the word is placed. + * @param col The column index where the word is placed. + * @param vertical If true, the word was placed vertically; otherwise, horizontally. + */ + public static void removeWord(char[][] puzzle, String word, int row, int col, boolean vertical) { + for (int i = 0; i < word.length(); i++) { + if (vertical) { + puzzle[row + i][col] = ' '; + } else { + puzzle[row][col + i] = ' '; + } + } + } + + /** + * Solves the crossword puzzle using backtracking. + * + * @param puzzle The crossword puzzle represented as a 2D char array. + * @param words The list of words to be placed. + * @return true if the crossword is solved, false otherwise. + */ + public static boolean solveCrossword(char[][] puzzle, Collection words) { + // Create a mutable copy of the words list + List remainingWords = new ArrayList<>(words); + + for (int row = 0; row < puzzle.length; row++) { + for (int col = 0; col < puzzle[0].length; col++) { + if (puzzle[row][col] == ' ') { + for (String word : new ArrayList<>(remainingWords)) { + for (boolean vertical : new boolean[] {true, false}) { + if (isValid(puzzle, word, row, col, vertical)) { + placeWord(puzzle, word, row, col, vertical); + remainingWords.remove(word); + if (solveCrossword(puzzle, remainingWords)) { + return true; + } + remainingWords.add(word); + removeWord(puzzle, word, row, col, vertical); + } + } + } + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java b/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java index f7eae01e449a..8247172e7ee0 100644 --- a/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java +++ b/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java @@ -1,152 +1,125 @@ package com.thealgorithms.backtracking; +/** + * This class contains methods to solve a maze using recursive backtracking. + * The maze is represented as a 2D array where walls, paths, and visited/dead + * ends are marked with different integers. + * + * The goal is to find a path from a starting position to the target position + * (map[6][5]) while navigating through the maze. + */ public final class MazeRecursion { + private MazeRecursion() { } - public static void mazeRecursion() { - // First create a 2 dimensions array to mimic a maze map - int[][] map = new int[8][7]; - int[][] map2 = new int[8][7]; - - // We use 1 to indicate wall - // Set the ceiling and floor to 1 - for (int i = 0; i < 7; i++) { - map[0][i] = 1; - map[7][i] = 1; - } - - // Then we set the left and right wall to 1 - for (int i = 0; i < 8; i++) { - map[i][0] = 1; - map[i][6] = 1; - } - - // Now we have created a maze with its wall initialized - - // Here we set the obstacle - map[3][1] = 1; - map[3][2] = 1; - - // Print the current map - System.out.println("The condition of the map: "); - for (int i = 0; i < 8; i++) { - for (int j = 0; j < 7; j++) { - System.out.print(map[i][j] + " "); - } - System.out.println(); - } - - // clone another map for setWay2 method - for (int i = 0; i < map.length; i++) { - System.arraycopy(map[i], 0, map2[i], 0, map[i].length); - } - - // By using recursive backtracking to let your ball(target) find its way in the - // maze - // The first parameter is the map - // Second parameter is x coordinate of your target - // Third parameter is the y coordinate of your target - setWay(map, 1, 1); - setWay2(map2, 1, 1); - - // Print out the new map1, with the ball footprint - System.out.println("After the ball goes through the map1,show the current map1 condition"); - for (int i = 0; i < 8; i++) { - for (int j = 0; j < 7; j++) { - System.out.print(map[i][j] + " "); - } - System.out.println(); + /** + * This method solves the maze using the "down -> right -> up -> left" + * movement strategy. + * + * @param map The 2D array representing the maze (walls, paths, etc.) + * @return The solved maze with paths marked, or null if no solution exists. + */ + public static int[][] solveMazeUsingFirstStrategy(int[][] map) { + if (setWay(map, 1, 1)) { + return map; } + return null; + } - // Print out the new map2, with the ball footprint - System.out.println("After the ball goes through the map2,show the current map2 condition"); - for (int i = 0; i < 8; i++) { - for (int j = 0; j < 7; j++) { - System.out.print(map2[i][j] + " "); - } - System.out.println(); + /** + * This method solves the maze using the "up -> right -> down -> left" + * movement strategy. + * + * @param map The 2D array representing the maze (walls, paths, etc.) + * @return The solved maze with paths marked, or null if no solution exists. + */ + public static int[][] solveMazeUsingSecondStrategy(int[][] map) { + if (setWay2(map, 1, 1)) { + return map; } + return null; } /** - * Using recursive path finding to help the ball find its way in the maze - * Description: - * 1. map (means the maze) - * 2. i, j (means the initial coordinate of the ball in the maze) - * 3. if the ball can reach the end of maze, that is position of map[6][5], - * means the we have found a path for the ball - * 4. Additional Information: 0 in the map[i][j] means the ball has not gone - * through this position, 1 means the wall, 2 means the path is feasible, 3 - * means the ball has gone through the path but this path is dead end - * 5. We will need strategy for the ball to pass through the maze for example: - * Down -> Right -> Up -> Left, if the path doesn't work, then backtrack + * Attempts to find a path through the maze using a "down -> right -> up -> left" + * movement strategy. The path is marked with '2' for valid paths and '3' for dead ends. * - * @author OngLipWei - * @version Jun 23, 2021 11:36:14 AM - * @param map The maze - * @param i x coordinate of your ball(target) - * @param j y coordinate of your ball(target) - * @return If we did find a path for the ball,return true,else false + * @param map The 2D array representing the maze (walls, paths, etc.) + * @param i The current x-coordinate of the ball (row index) + * @param j The current y-coordinate of the ball (column index) + * @return True if a path is found to (6,5), otherwise false */ - public static boolean setWay(int[][] map, int i, int j) { - if (map[6][5] == 2) { // means the ball find its path, ending condition + private static boolean setWay(int[][] map, int i, int j) { + if (map[6][5] == 2) { return true; } - if (map[i][j] == 0) { // if the ball haven't gone through this point - // then the ball follows the move strategy : down -> right -> up -> left - map[i][j] = 2; // we assume that this path is feasible first, set the current point to 2 - // first。 - if (setWay(map, i + 1, j)) { // go down + + // If the current position is unvisited (0), explore it + if (map[i][j] == 0) { + // Mark the current position as '2' + map[i][j] = 2; + + // Move down + if (setWay(map, i + 1, j)) { return true; - } else if (setWay(map, i, j + 1)) { // go right + } + // Move right + else if (setWay(map, i, j + 1)) { return true; - } else if (setWay(map, i - 1, j)) { // go up + } + // Move up + else if (setWay(map, i - 1, j)) { return true; - } else if (setWay(map, i, j - 1)) { // go left + } + // Move left + else if (setWay(map, i, j - 1)) { return true; - } else { - // means that the current point is the dead end, the ball cannot proceed, set - // the current point to 3 and return false, the backtracking will start, it will - // go to the previous step and check for feasible path again - map[i][j] = 3; - return false; } - } else { // if the map[i][j] != 0 , it will probably be 1,2,3, return false because the - // ball cannot hit the wall, cannot go to the path that has gone though before, - // and cannot head to deadened. + + map[i][j] = 3; // Mark as dead end (3) if no direction worked return false; } + return false; } - // Here is another move strategy for the ball: up->right->down->left - public static boolean setWay2(int[][] map, int i, int j) { - if (map[6][5] == 2) { // means the ball find its path, ending condition + /** + * Attempts to find a path through the maze using an alternative movement + * strategy "up -> right -> down -> left". + * + * @param map The 2D array representing the maze (walls, paths, etc.) + * @param i The current x-coordinate of the ball (row index) + * @param j The current y-coordinate of the ball (column index) + * @return True if a path is found to (6,5), otherwise false + */ + private static boolean setWay2(int[][] map, int i, int j) { + if (map[6][5] == 2) { return true; } - if (map[i][j] == 0) { // if the ball haven't gone through this point - // then the ball follows the move strategy : up->right->down->left - map[i][j] = 2; // we assume that this path is feasible first, set the current point to 2 - // first。 - if (setWay2(map, i - 1, j)) { // go up + + if (map[i][j] == 0) { + map[i][j] = 2; + + // Move up + if (setWay2(map, i - 1, j)) { return true; - } else if (setWay2(map, i, j + 1)) { // go right + } + // Move right + else if (setWay2(map, i, j + 1)) { return true; - } else if (setWay2(map, i + 1, j)) { // go down + } + // Move down + else if (setWay2(map, i + 1, j)) { return true; - } else if (setWay2(map, i, j - 1)) { // go left + } + // Move left + else if (setWay2(map, i, j - 1)) { return true; - } else { - // means that the current point is the dead end, the ball cannot proceed, set - // the current point to 3 and return false, the backtracking will start, it will - // go to the previous step and check for feasible path again - map[i][j] = 3; - return false; } - } else { // if the map[i][j] != 0 , it will probably be 1,2,3, return false because the - // ball cannot hit the wall, cannot go to the path that has gone through before, - // and cannot head to deadend. + + map[i][j] = 3; // Mark as dead end (3) if no direction worked return false; } + return false; } } diff --git a/src/main/java/com/thealgorithms/backtracking/PowerSum.java b/src/main/java/com/thealgorithms/backtracking/PowerSum.java index 6617ea326a1c..b34ba660ebd7 100644 --- a/src/main/java/com/thealgorithms/backtracking/PowerSum.java +++ b/src/main/java/com/thealgorithms/backtracking/PowerSum.java @@ -1,45 +1,51 @@ package com.thealgorithms.backtracking; -/* - * Problem Statement : - * Find the number of ways that a given integer, N , can be expressed as the sum of the Xth powers - * of unique, natural numbers. For example, if N=100 and X=3, we have to find all combinations of - * unique cubes adding up to 100. The only solution is 1^3+2^3+3^3+4^3. Therefore output will be 1. +/** + * Problem Statement: + * Find the number of ways that a given integer, N, can be expressed as the sum of the Xth powers + * of unique, natural numbers. + * For example, if N=100 and X=3, we have to find all combinations of unique cubes adding up to 100. + * The only solution is 1^3 + 2^3 + 3^3 + 4^3. Therefore, the output will be 1. + * + * N is represented by the parameter 'targetSum' in the code. + * X is represented by the parameter 'power' in the code. */ public class PowerSum { - private int count = 0; - private int sum = 0; - - public int powSum(int n, int x) { - sum(n, x, 1); - return count; + /** + * Calculates the number of ways to express the target sum as a sum of Xth powers of unique natural numbers. + * + * @param targetSum The target sum to achieve (N in the problem statement) + * @param power The power to raise natural numbers to (X in the problem statement) + * @return The number of ways to express the target sum + */ + public int powSum(int targetSum, int power) { + // Special case: when both targetSum and power are zero + if (targetSum == 0 && power == 0) { + return 1; // by convention, one way to sum to zero: use nothing + } + return sumRecursive(targetSum, power, 1, 0); } - // here i is the natural number which will be raised by X and added in sum. - public void sum(int n, int x, int i) { - // if sum is equal to N that is one of our answer and count is increased. - if (sum == n) { - count++; - return; - } // we will be adding next natural number raised to X only if on adding it in sum the - // result is less than N. - else if (sum + power(i, x) <= n) { - sum += power(i, x); - sum(n, x, i + 1); - // backtracking and removing the number added last since no possible combination is - // there with it. - sum -= power(i, x); + /** + * Recursively calculates the number of ways to express the remaining sum as a sum of Xth powers. + * + * @param remainingSum The remaining sum to achieve + * @param power The power to raise natural numbers to (X in the problem statement) + * @param currentNumber The current natural number being considered + * @param currentSum The current sum of powered numbers + * @return The number of valid combinations + */ + private int sumRecursive(int remainingSum, int power, int currentNumber, int currentSum) { + int newSum = currentSum + (int) Math.pow(currentNumber, power); + + if (newSum == remainingSum) { + return 1; } - if (power(i, x) < n) { - // calling the sum function with next natural number after backtracking if when it is - // raised to X is still less than X. - sum(n, x, i + 1); + if (newSum > remainingSum) { + return 0; } - } - // creating a separate power function so that it can be used again and again when required. - private int power(int a, int b) { - return (int) Math.pow(a, b); + return sumRecursive(remainingSum, power, currentNumber + 1, newSum) + sumRecursive(remainingSum, power, currentNumber + 1, currentSum); } } diff --git a/src/main/java/com/thealgorithms/backtracking/WordPatternMatcher.java b/src/main/java/com/thealgorithms/backtracking/WordPatternMatcher.java new file mode 100644 index 000000000000..1854cab20a7f --- /dev/null +++ b/src/main/java/com/thealgorithms/backtracking/WordPatternMatcher.java @@ -0,0 +1,86 @@ +package com.thealgorithms.backtracking; + +import java.util.HashMap; +import java.util.Map; + +/** + * Class to determine if a pattern matches a string using backtracking. + * + * Example: + * Pattern: "abab" + * Input String: "JavaPythonJavaPython" + * Output: true + * + * Pattern: "aaaa" + * Input String: "JavaJavaJavaJava" + * Output: true + * + * Pattern: "aabb" + * Input String: "JavaPythonPythonJava" + * Output: false + */ +public final class WordPatternMatcher { + private WordPatternMatcher() { + } + + /** + * Determines if the given pattern matches the input string using backtracking. + * + * @param pattern The pattern to match. + * @param inputString The string to match against the pattern. + * @return True if the pattern matches the string, False otherwise. + */ + public static boolean matchWordPattern(String pattern, String inputString) { + Map patternMap = new HashMap<>(); + Map strMap = new HashMap<>(); + return backtrack(pattern, inputString, 0, 0, patternMap, strMap); + } + + /** + * Backtracking helper function to check if the pattern matches the string. + * + * @param pattern The pattern string. + * @param inputString The string to match against the pattern. + * @param patternIndex Current index in the pattern. + * @param strIndex Current index in the input string. + * @param patternMap Map to store pattern characters to string mappings. + * @param strMap Map to store string to pattern character mappings. + * @return True if the pattern matches, False otherwise. + */ + private static boolean backtrack(String pattern, String inputString, int patternIndex, int strIndex, Map patternMap, Map strMap) { + if (patternIndex == pattern.length() && strIndex == inputString.length()) { + return true; + } + if (patternIndex == pattern.length() || strIndex == inputString.length()) { + return false; + } + + char currentChar = pattern.charAt(patternIndex); + if (patternMap.containsKey(currentChar)) { + String mappedStr = patternMap.get(currentChar); + if (inputString.startsWith(mappedStr, strIndex)) { + return backtrack(pattern, inputString, patternIndex + 1, strIndex + mappedStr.length(), patternMap, strMap); + } else { + return false; + } + } + + for (int end = strIndex + 1; end <= inputString.length(); end++) { + String substring = inputString.substring(strIndex, end); + if (strMap.containsKey(substring)) { + continue; + } + + patternMap.put(currentChar, substring); + strMap.put(substring, currentChar); + if (backtrack(pattern, inputString, patternIndex + 1, end, patternMap, strMap)) { + return true; + } + + patternMap.remove(currentChar); + strMap.remove(substring); + } + + return false; + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/BcdConversion.java b/src/main/java/com/thealgorithms/bitmanipulation/BcdConversion.java new file mode 100644 index 000000000000..e6bd35720d9f --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/BcdConversion.java @@ -0,0 +1,82 @@ +package com.thealgorithms.bitmanipulation; + +/** + * This class provides methods to convert between BCD (Binary-Coded Decimal) and decimal numbers. + * + * BCD is a class of binary encodings of decimal numbers where each decimal digit is represented by a fixed number of binary digits, usually four or eight. + * + * For more information, refer to the + * Binary-Coded Decimal Wikipedia page. + * + * Example usage: + *

+ * int decimal = BcdConversion.bcdToDecimal(0x1234);
+ * System.out.println("BCD 0x1234 to decimal: " + decimal); // Output: 1234
+ *
+ * int bcd = BcdConversion.decimalToBcd(1234);
+ * System.out.println("Decimal 1234 to BCD: " + Integer.toHexString(bcd)); // Output: 0x1234
+ * 
+ */ +public final class BcdConversion { + private BcdConversion() { + } + + /** + * Converts a BCD (Binary-Coded Decimal) number to a decimal number. + *

Steps: + *

1. Validate the BCD number to ensure all digits are between 0 and 9. + *

2. Extract the last 4 bits (one BCD digit) from the BCD number. + *

3. Multiply the extracted digit by the corresponding power of 10 and add it to the decimal number. + *

4. Shift the BCD number right by 4 bits to process the next BCD digit. + *

5. Repeat steps 1-4 until the BCD number is zero. + * + * @param bcd The BCD number. + * @return The corresponding decimal number. + * @throws IllegalArgumentException if the BCD number contains invalid digits. + */ + public static int bcdToDecimal(int bcd) { + int decimal = 0; + int multiplier = 1; + + // Validate BCD digits + while (bcd > 0) { + int digit = bcd & 0xF; + if (digit > 9) { + throw new IllegalArgumentException("Invalid BCD digit: " + digit); + } + decimal += digit * multiplier; + multiplier *= 10; + bcd >>= 4; + } + return decimal; + } + + /** + * Converts a decimal number to BCD (Binary-Coded Decimal). + *

Steps: + *

1. Check if the decimal number is within the valid range for BCD (0 to 9999). + *

2. Extract the last decimal digit from the decimal number. + *

3. Shift the digit to the correct BCD position and add it to the BCD number. + *

4. Remove the last decimal digit from the decimal number. + *

5. Repeat steps 2-4 until the decimal number is zero. + * + * @param decimal The decimal number. + * @return The corresponding BCD number. + * @throws IllegalArgumentException if the decimal number is greater than 9999. + */ + public static int decimalToBcd(int decimal) { + if (decimal < 0 || decimal > 9999) { + throw new IllegalArgumentException("Value out of bounds for BCD representation: " + decimal); + } + + int bcd = 0; + int shift = 0; + while (decimal > 0) { + int digit = decimal % 10; + bcd |= (digit << (shift * 4)); + decimal /= 10; + shift++; + } + return bcd; + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java b/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java new file mode 100644 index 000000000000..0d6fd140c720 --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java @@ -0,0 +1,43 @@ +package com.thealgorithms.bitmanipulation; + +/** + * This class contains a method to check if the binary representation of a number is a palindrome. + *

+ * A binary palindrome is a number whose binary representation is the same when read from left to right and right to left. + * For example, the number 9 has a binary representation of 1001, which is a palindrome. + * The number 10 has a binary representation of 1010, which is not a palindrome. + *

+ * + * @author Hardvan + */ +public final class BinaryPalindromeCheck { + private BinaryPalindromeCheck() { + } + + /** + * Checks if the binary representation of a number is a palindrome. + * + * @param x The number to check. + * @return True if the binary representation is a palindrome, otherwise false. + */ + public static boolean isBinaryPalindrome(int x) { + int reversed = reverseBits(x); + return x == reversed; + } + + /** + * Helper function to reverse all the bits of an integer. + * + * @param x The number to reverse the bits of. + * @return The number with reversed bits. + */ + private static int reverseBits(int x) { + int result = 0; + while (x > 0) { + result <<= 1; + result |= (x & 1); + x >>= 1; + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/BooleanAlgebraGates.java b/src/main/java/com/thealgorithms/bitmanipulation/BooleanAlgebraGates.java new file mode 100644 index 000000000000..869466320831 --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/BooleanAlgebraGates.java @@ -0,0 +1,111 @@ +package com.thealgorithms.bitmanipulation; + +import java.util.List; + +/** + * Implements various Boolean algebra gates (AND, OR, NOT, XOR, NAND, NOR) + */ +public final class BooleanAlgebraGates { + + private BooleanAlgebraGates() { + // Prevent instantiation + } + + /** + * Represents a Boolean gate that takes multiple inputs and returns a result. + */ + interface BooleanGate { + /** + * Evaluates the gate with the given inputs. + * + * @param inputs The input values for the gate. + * @return The result of the evaluation. + */ + boolean evaluate(List inputs); + } + + /** + * AND Gate implementation. + * Returns true if all inputs are true; otherwise, false. + */ + static class ANDGate implements BooleanGate { + @Override + public boolean evaluate(List inputs) { + for (boolean input : inputs) { + if (!input) { + return false; + } + } + return true; + } + } + + /** + * OR Gate implementation. + * Returns true if at least one input is true; otherwise, false. + */ + static class ORGate implements BooleanGate { + @Override + public boolean evaluate(List inputs) { + for (boolean input : inputs) { + if (input) { + return true; + } + } + return false; + } + } + + /** + * NOT Gate implementation (Unary operation). + * Negates a single input value. + */ + static class NOTGate { + /** + * Evaluates the negation of the input. + * + * @param input The input value to be negated. + * @return The negated value. + */ + public boolean evaluate(boolean input) { + return !input; + } + } + + /** + * XOR Gate implementation. + * Returns true if an odd number of inputs are true; otherwise, false. + */ + static class XORGate implements BooleanGate { + @Override + public boolean evaluate(List inputs) { + boolean result = false; + for (boolean input : inputs) { + result ^= input; + } + return result; + } + } + + /** + * NAND Gate implementation. + * Returns true if at least one input is false; otherwise, false. + */ + static class NANDGate implements BooleanGate { + @Override + public boolean evaluate(List inputs) { + return !new ANDGate().evaluate(inputs); // Equivalent to negation of AND + } + } + + /** + * NOR Gate implementation. + * Returns true if all inputs are false; otherwise, false. + */ + static class NORGate implements BooleanGate { + @Override + public boolean evaluate(List inputs) { + return !new ORGate().evaluate(inputs); // Equivalent to negation of OR + } + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/ClearLeftmostSetBit.java b/src/main/java/com/thealgorithms/bitmanipulation/ClearLeftmostSetBit.java new file mode 100644 index 000000000000..3e9a4a21183f --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/ClearLeftmostSetBit.java @@ -0,0 +1,39 @@ +package com.thealgorithms.bitmanipulation; + +/** + * ClearLeftmostSetBit class contains a method to clear the leftmost set bit of a number. + * The leftmost set bit is the leftmost bit that is set to 1 in the binary representation of a number. + * + * Example: + * 26 (11010) -> 10 (01010) + * 1 (1) -> 0 (0) + * 7 (111) -> 3 (011) + * 6 (0110) -> 2 (0010) + * + * @author Hardvan + */ +public final class ClearLeftmostSetBit { + private ClearLeftmostSetBit() { + } + + /** + * Clears the leftmost set bit (1) of a given number. + * Step 1: Find the position of the leftmost set bit + * Step 2: Create a mask with all bits set except for the leftmost set bit + * Step 3: Clear the leftmost set bit using AND with the mask + * + * @param num The input number. + * @return The number after clearing the leftmost set bit. + */ + public static int clearLeftmostSetBit(int num) { + int pos = 0; + int temp = num; + while (temp > 0) { + temp >>= 1; + pos++; + } + + int mask = ~(1 << (pos - 1)); + return num & mask; + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/CountLeadingZeros.java b/src/main/java/com/thealgorithms/bitmanipulation/CountLeadingZeros.java new file mode 100644 index 000000000000..318334f0b951 --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/CountLeadingZeros.java @@ -0,0 +1,39 @@ +package com.thealgorithms.bitmanipulation; + +/** + * CountLeadingZeros class contains a method to count the number of leading zeros in the binary representation of a number. + * The number of leading zeros is the number of zeros before the leftmost 1 bit. + * For example, the number 5 has 29 leading zeros in its 32-bit binary representation. + * The number 0 has 32 leading zeros. + * The number 1 has 31 leading zeros. + * The number -1 has no leading zeros. + * + * @author Hardvan + */ +public final class CountLeadingZeros { + private CountLeadingZeros() { + } + + /** + * Counts the number of leading zeros in the binary representation of a number. + * Method: Keep shifting the mask to the right until the leftmost bit is 1. + * The number of shifts is the number of leading zeros. + * + * @param num The input number. + * @return The number of leading zeros. + */ + public static int countLeadingZeros(int num) { + if (num == 0) { + return 32; + } + + int count = 0; + int mask = 1 << 31; + while ((mask & num) == 0) { + count++; + mask >>>= 1; + } + + return count; + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/FindNthBit.java b/src/main/java/com/thealgorithms/bitmanipulation/FindNthBit.java new file mode 100644 index 000000000000..7a35fc3feebf --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/FindNthBit.java @@ -0,0 +1,46 @@ +package com.thealgorithms.bitmanipulation; + +/** + * A utility class to find the Nth bit of a given number. + * + *

This class provides a method to extract the value of the Nth bit (either 0 or 1) + * from the binary representation of a given integer. + * + *

Example: + *

{@code
+ * int result = FindNthBit.findNthBit(5, 2); // returns 0 as the 2nd bit of 5 (binary 101) is 0.
+ * }
+ * + *

Author: Tuhinm2002 + */ +public final class FindNthBit { + + /** + * Private constructor to prevent instantiation. + * + *

This is a utility class, and it should not be instantiated. + * Attempting to instantiate this class will throw an UnsupportedOperationException. + */ + private FindNthBit() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * Finds the value of the Nth bit of the given number. + * + *

This method uses bitwise operations to extract the Nth bit from the + * binary representation of the given integer. + * + * @param num the integer number whose Nth bit is to be found + * @param n the bit position (1-based) to retrieve + * @return the value of the Nth bit (0 or 1) + * @throws IllegalArgumentException if the bit position is less than 1 + */ + public static int findNthBit(int num, int n) { + if (n < 1) { + throw new IllegalArgumentException("Bit position must be greater than or equal to 1."); + } + // Shifting the number to the right by (n - 1) positions and checking the last bit + return (num & (1 << (n - 1))) >> (n - 1); + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/FirstDifferentBit.java b/src/main/java/com/thealgorithms/bitmanipulation/FirstDifferentBit.java new file mode 100644 index 000000000000..9a761c572e2c --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/FirstDifferentBit.java @@ -0,0 +1,33 @@ +package com.thealgorithms.bitmanipulation; + +/** + * This class provides a method to find the first differing bit + * between two integers. + * + * Example: + * x = 10 (1010 in binary) + * y = 12 (1100 in binary) + * The first differing bit is at index 1 (0-based) + * So, the output will be 1 + * + * @author Hardvan + */ +public final class FirstDifferentBit { + private FirstDifferentBit() { + } + + /** + * Identifies the index of the first differing bit between two integers. + * Steps: + * 1. XOR the two integers to get the differing bits + * 2. Find the index of the first set bit in XOR result + * + * @param x the first integer + * @param y the second integer + * @return the index of the first differing bit (0-based) + */ + public static int firstDifferentBit(int x, int y) { + int diff = x ^ y; + return Integer.numberOfTrailingZeros(diff); + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/GenerateSubsets.java b/src/main/java/com/thealgorithms/bitmanipulation/GenerateSubsets.java new file mode 100644 index 000000000000..f1b812495c1b --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/GenerateSubsets.java @@ -0,0 +1,44 @@ +package com.thealgorithms.bitmanipulation; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class provides a method to generate all subsets (power set) + * of a given set using bit manipulation. + * + * @author Hardvan + */ +public final class GenerateSubsets { + private GenerateSubsets() { + } + + /** + * Generates all subsets of a given set using bit manipulation. + * Steps: + * 1. Iterate over all numbers from 0 to 2^n - 1. + * 2. For each number, iterate over all bits from 0 to n - 1. + * 3. If the i-th bit of the number is set, add the i-th element of the set to the current subset. + * 4. Add the current subset to the list of subsets. + * 5. Return the list of subsets. + * + * @param set the input set of integers + * @return a list of all subsets represented as lists of integers + */ + public static List> generateSubsets(int[] set) { + int n = set.length; + List> subsets = new ArrayList<>(); + + for (int mask = 0; mask < (1 << n); mask++) { + List subset = new ArrayList<>(); + for (int i = 0; i < n; i++) { + if ((mask & (1 << i)) != 0) { + subset.add(set[i]); + } + } + subsets.add(subset); + } + + return subsets; + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/GrayCodeConversion.java b/src/main/java/com/thealgorithms/bitmanipulation/GrayCodeConversion.java new file mode 100644 index 000000000000..83cd30c7d50a --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/GrayCodeConversion.java @@ -0,0 +1,44 @@ +package com.thealgorithms.bitmanipulation; + +/** + * Gray code is a binary numeral system where two successive values differ in only one bit. + * This is a simple conversion between binary and Gray code. + * Example: + * 7 -> 0111 -> 0100 -> 4 + * 4 -> 0100 -> 0111 -> 7 + * 0 -> 0000 -> 0000 -> 0 + * 1 -> 0001 -> 0000 -> 0 + * 2 -> 0010 -> 0011 -> 3 + * 3 -> 0011 -> 0010 -> 2 + * + * @author Hardvan + */ +public final class GrayCodeConversion { + private GrayCodeConversion() { + } + + /** + * Converts a binary number to Gray code. + * + * @param num The binary number. + * @return The corresponding Gray code. + */ + public static int binaryToGray(int num) { + return num ^ (num >> 1); + } + + /** + * Converts a Gray code number back to binary. + * + * @param gray The Gray code number. + * @return The corresponding binary number. + */ + public static int grayToBinary(int gray) { + int binary = gray; + while (gray > 0) { + gray >>= 1; + binary ^= gray; + } + return binary; + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/HammingDistance.java b/src/main/java/com/thealgorithms/bitmanipulation/HammingDistance.java new file mode 100644 index 000000000000..4c24909ef234 --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/HammingDistance.java @@ -0,0 +1,29 @@ +package com.thealgorithms.bitmanipulation; + +/** + * The Hamming distance between two integers is the number of positions at which the corresponding bits are different. + * Given two integers x and y, calculate the Hamming distance. + * Example: + * Input: x = 1, y = 4 + * Output: 2 + * Explanation: 1 (0001) and 4 (0100) have 2 differing bits. + * + * @author Hardvan + */ +public final class HammingDistance { + private HammingDistance() { + } + + /** + * Calculates the Hamming distance between two integers. + * The Hamming distance is the number of differing bits between the two integers. + * + * @param x The first integer. + * @param y The second integer. + * @return The Hamming distance (number of differing bits). + */ + public static int hammingDistance(int x, int y) { + int xor = x ^ y; + return Integer.bitCount(xor); + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/HigherLowerPowerOfTwo.java b/src/main/java/com/thealgorithms/bitmanipulation/HigherLowerPowerOfTwo.java new file mode 100644 index 000000000000..0fb058b2b8a3 --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/HigherLowerPowerOfTwo.java @@ -0,0 +1,54 @@ +package com.thealgorithms.bitmanipulation; + +/** + * HigherLowerPowerOfTwo class has two methods to find the next higher and lower power of two. + *

+ * nextHigherPowerOfTwo method finds the next higher power of two. + * nextLowerPowerOfTwo method finds the next lower power of two. + * Both methods take an integer as input and return the next higher or lower power of two. + * If the input is less than 1, the next higher power of two is 1. + * If the input is less than or equal to 1, the next lower power of two is 0. + * nextHigherPowerOfTwo method uses bitwise operations to find the next higher power of two. + * nextLowerPowerOfTwo method uses Integer.highestOneBit method to find the next lower power of two. + * The time complexity of both methods is O(1). + * The space complexity of both methods is O(1). + *

+ * + * @author Hardvan + */ +public final class HigherLowerPowerOfTwo { + private HigherLowerPowerOfTwo() { + } + + /** + * Finds the next higher power of two. + * + * @param x The given number. + * @return The next higher power of two. + */ + public static int nextHigherPowerOfTwo(int x) { + if (x < 1) { + return 1; + } + x--; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return x + 1; + } + + /** + * Finds the next lower power of two. + * + * @param x The given number. + * @return The next lower power of two. + */ + public static int nextLowerPowerOfTwo(int x) { + if (x < 1) { + return 0; + } + return Integer.highestOneBit(x); + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/IsPowerTwo.java b/src/main/java/com/thealgorithms/bitmanipulation/IsPowerTwo.java index 54d28d4d22cc..4cdf3c6faa3e 100644 --- a/src/main/java/com/thealgorithms/bitmanipulation/IsPowerTwo.java +++ b/src/main/java/com/thealgorithms/bitmanipulation/IsPowerTwo.java @@ -1,13 +1,27 @@ package com.thealgorithms.bitmanipulation; /** - * Is number power of 2 + * Utility class for checking if a number is a power of two. + * A power of two is a number that can be expressed as 2^n where n is a non-negative integer. + * This class provides a method to determine if a given integer is a power of two using bit manipulation. + * * @author Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi) */ - public final class IsPowerTwo { private IsPowerTwo() { } + + /** + * Checks if the given integer is a power of two. + * + * A number is considered a power of two if it is greater than zero and + * has exactly one '1' bit in its binary representation. This method + * uses the property that for any power of two (n), the expression + * (n & (n - 1)) will be zero. + * + * @param number the integer to check + * @return true if the number is a power of two, false otherwise + */ public static boolean isPowerTwo(int number) { if (number <= 0) { return false; diff --git a/src/main/java/com/thealgorithms/bitmanipulation/ModuloPowerOfTwo.java b/src/main/java/com/thealgorithms/bitmanipulation/ModuloPowerOfTwo.java new file mode 100644 index 000000000000..537a046f77e4 --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/ModuloPowerOfTwo.java @@ -0,0 +1,28 @@ +package com.thealgorithms.bitmanipulation; + +/** + * This class provides a method to compute the remainder + * of a number when divided by a power of two (2^n) + * without using division or modulo operations. + * + * @author Hardvan + */ +public final class ModuloPowerOfTwo { + private ModuloPowerOfTwo() { + } + + /** + * Computes the remainder of a given integer when divided by 2^n. + * + * @param x the input number + * @param n the exponent (power of two) + * @return the remainder of x divided by 2^n + */ + public static int moduloPowerOfTwo(int x, int n) { + if (n <= 0) { + throw new IllegalArgumentException("The exponent must be positive"); + } + + return x & ((1 << n) - 1); + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/NextHigherSameBitCount.java b/src/main/java/com/thealgorithms/bitmanipulation/NextHigherSameBitCount.java new file mode 100644 index 000000000000..6a764d806279 --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/NextHigherSameBitCount.java @@ -0,0 +1,30 @@ +package com.thealgorithms.bitmanipulation; + +/** + * This class provides a method to find the next higher number + * with the same number of set bits as the given number. + * + * @author Hardvan + */ +public final class NextHigherSameBitCount { + private NextHigherSameBitCount() { + } + + /** + * Finds the next higher integer with the same number of set bits. + * Steps: + * 1. Find {@code c}, the rightmost set bit of {@code n}. + * 2. Find {@code r}, the rightmost set bit of {@code n + c}. + * 3. Swap the bits of {@code r} and {@code n} to the right of {@code c}. + * 4. Shift the bits of {@code r} and {@code n} to the right of {@code c} to the rightmost. + * 5. Combine the results of steps 3 and 4. + * + * @param n the input number + * @return the next higher integer with the same set bit count + */ + public static int nextHigherSameBitCount(int n) { + int c = n & -n; + int r = n + c; + return (((r ^ n) >> 2) / c) | r; + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinder.java b/src/main/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinder.java index 07476a8b9476..17e1a73ec062 100644 --- a/src/main/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinder.java +++ b/src/main/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinder.java @@ -1,14 +1,30 @@ package com.thealgorithms.bitmanipulation; /** - * Find Non Repeating Number + * A utility class to find the non-repeating number in an array where every other number repeats. + * This class contains a method to identify the single unique number using bit manipulation. + * + * The solution leverages the properties of the XOR operation, which states that: + * - x ^ x = 0 for any integer x (a number XORed with itself is zero) + * - x ^ 0 = x for any integer x (a number XORed with zero is the number itself) + * + * Using these properties, we can find the non-repeating number in linear time with constant space. + * + * Example: + * Given the input array [2, 3, 5, 2, 3], the output will be 5 since it does not repeat. + * * @author Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi) */ - public final class NonRepeatingNumberFinder { private NonRepeatingNumberFinder() { } + /** + * Finds the non-repeating number in the given array. + * + * @param arr an array of integers where every number except one appears twice + * @return the integer that appears only once in the array or 0 if the array is empty + */ public static int findNonRepeatingNumber(int[] arr) { int result = 0; for (int num : arr) { diff --git a/src/main/java/com/thealgorithms/bitmanipulation/NumberAppearingOddTimes.java b/src/main/java/com/thealgorithms/bitmanipulation/NumberAppearingOddTimes.java new file mode 100644 index 000000000000..bd4868d4dbd5 --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/NumberAppearingOddTimes.java @@ -0,0 +1,41 @@ +package com.thealgorithms.bitmanipulation; + +/** + * This class provides a method to find the element that appears an + * odd number of times in an array. All other elements in the array + * must appear an even number of times for the logic to work. + * + * The solution uses the XOR operation, which has the following properties: + * - a ^ a = 0 (XOR-ing the same numbers cancels them out) + * - a ^ 0 = a + * - XOR is commutative and associative. + * + * Time Complexity: O(n), where n is the size of the array. + * Space Complexity: O(1), as no extra space is used. + * + * Usage Example: + * int result = NumberAppearingOddTimes.findOddOccurrence(new int[]{1, 2, 1, 2, 3}); + * // result will be 3 + * + * @author Lakshyajeet Singh Goyal (https://github.com/DarkMatter-999) + */ + +public final class NumberAppearingOddTimes { + private NumberAppearingOddTimes() { + } + + /** + * Finds the element in the array that appears an odd number of times. + * + * @param arr the input array containing integers, where all elements + * except one appear an even number of times. + * @return the integer that appears an odd number of times. + */ + public static int findOddOccurrence(int[] arr) { + int result = 0; + for (int num : arr) { + result ^= num; + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/NumbersDifferentSigns.java b/src/main/java/com/thealgorithms/bitmanipulation/NumbersDifferentSigns.java index 8e0946f0eb23..a2da37aa81ee 100644 --- a/src/main/java/com/thealgorithms/bitmanipulation/NumbersDifferentSigns.java +++ b/src/main/java/com/thealgorithms/bitmanipulation/NumbersDifferentSigns.java @@ -1,14 +1,29 @@ package com.thealgorithms.bitmanipulation; /** - * Numbers Different Signs + * This class provides a method to determine whether two integers have + * different signs. It utilizes the XOR operation on the two numbers: + * + * - If two numbers have different signs, their most significant bits + * (sign bits) will differ, resulting in a negative XOR result. + * - If two numbers have the same sign, the XOR result will be non-negative. + * + * Time Complexity: O(1) - Constant time operation. + * Space Complexity: O(1) - No extra space used. + * * @author Bama Charan Chhandogi */ - public final class NumbersDifferentSigns { private NumbersDifferentSigns() { } + /** + * Determines if two integers have different signs using bitwise XOR. + * + * @param num1 the first integer + * @param num2 the second integer + * @return true if the two numbers have different signs, false otherwise + */ public static boolean differentSigns(int num1, int num2) { return (num1 ^ num2) < 0; } diff --git a/src/main/java/com/thealgorithms/bitmanipulation/OneBitDifference.java b/src/main/java/com/thealgorithms/bitmanipulation/OneBitDifference.java new file mode 100644 index 000000000000..afec0188e299 --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/OneBitDifference.java @@ -0,0 +1,32 @@ +package com.thealgorithms.bitmanipulation; + +/** + * This class provides a method to detect if two integers + * differ by exactly one bit flip. + * + * Example: + * 1 (0001) and 2 (0010) differ by exactly one bit flip. + * 7 (0111) and 3 (0011) differ by exactly one bit flip. + * + * @author Hardvan + */ +public final class OneBitDifference { + private OneBitDifference() { + } + + /** + * Checks if two integers differ by exactly one bit. + * + * @param x the first integer + * @param y the second integer + * @return true if x and y differ by exactly one bit, false otherwise + */ + public static boolean differByOneBit(int x, int y) { + if (x == y) { + return false; + } + + int xor = x ^ y; + return (xor & (xor - 1)) == 0; + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/OnesComplement.java b/src/main/java/com/thealgorithms/bitmanipulation/OnesComplement.java new file mode 100644 index 000000000000..c5c068422113 --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/OnesComplement.java @@ -0,0 +1,28 @@ +package com.thealgorithms.bitmanipulation; + +/** + * @author - https://github.com/Monk-AbhinayVerma + * @Wikipedia - https://en.wikipedia.org/wiki/Ones%27_complement + * The class OnesComplement computes the complement of binary number + * and returns + * the complemented binary string. + * @return the complimented binary string + */ +public final class OnesComplement { + private OnesComplement() { + } + + // Function to get the 1's complement of a binary number + public static String onesComplement(String binary) { + StringBuilder complement = new StringBuilder(); + // Invert each bit to get the 1's complement + for (int i = 0; i < binary.length(); i++) { + if (binary.charAt(i) == '0') { + complement.append('1'); + } else { + complement.append('0'); + } + } + return complement.toString(); + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/ParityCheck.java b/src/main/java/com/thealgorithms/bitmanipulation/ParityCheck.java new file mode 100644 index 000000000000..5acab4d4a362 --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/ParityCheck.java @@ -0,0 +1,34 @@ +package com.thealgorithms.bitmanipulation; + +/** + * The ParityCheck class provides a method to check the parity of a given number. + *

+ * Parity is a mathematical term that describes the property of an integer's binary representation. + * The parity of a binary number is the number of 1s in its binary representation. + * If the number of 1s is even, the parity is even; otherwise, it is odd. + *

+ * For example, the binary representation of 5 is 101, which has two 1s, so the parity of 5 is even. + * The binary representation of 6 is 110, which has two 1s, so the parity of 6 is even. + * The binary representation of 7 is 111, which has three 1s, so the parity of 7 is odd. + * + * @author Hardvan + */ +public final class ParityCheck { + private ParityCheck() { + } + + /** + * This method checks the parity of the given number. + * + * @param n the number to check the parity of + * @return true if the number has even parity, false otherwise + */ + public static boolean checkParity(int n) { + int count = 0; + while (n > 0) { + count += n & 1; + n >>= 1; + } + return count % 2 == 0; + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/ReverseBits.java b/src/main/java/com/thealgorithms/bitmanipulation/ReverseBits.java index e8f2930d3afe..12c269d9be48 100644 --- a/src/main/java/com/thealgorithms/bitmanipulation/ReverseBits.java +++ b/src/main/java/com/thealgorithms/bitmanipulation/ReverseBits.java @@ -1,14 +1,33 @@ package com.thealgorithms.bitmanipulation; /** - * Converts any Octal Number to a Binary Number + * This class provides a method to reverse the bits of a 32-bit integer. + * Reversing the bits means that the least significant bit (LSB) becomes + * the most significant bit (MSB) and vice versa. + * + * Example: + * Input (binary): 00000010100101000001111010011100 (43261596) + * Output (binary): 00111001011110000010100101000000 (964176192) + * + * Time Complexity: O(32) - A fixed number of 32 iterations + * Space Complexity: O(1) - No extra space used + * + * Note: + * - If the input is negative, Java handles it using two’s complement representation. + * - This function works on 32-bit integers by default. + * * @author Bama Charan Chhandogi */ - public final class ReverseBits { private ReverseBits() { } + /** + * Reverses the bits of a 32-bit integer. + * + * @param n the integer whose bits are to be reversed + * @return the integer obtained by reversing the bits of the input + */ public static int reverseBits(int n) { int result = 0; int bitCount = 32; diff --git a/src/main/java/com/thealgorithms/bitmanipulation/SingleBitOperations.java b/src/main/java/com/thealgorithms/bitmanipulation/SingleBitOperations.java index b41aeca19af6..624a4e2b858a 100644 --- a/src/main/java/com/thealgorithms/bitmanipulation/SingleBitOperations.java +++ b/src/main/java/com/thealgorithms/bitmanipulation/SingleBitOperations.java @@ -1,34 +1,68 @@ package com.thealgorithms.bitmanipulation; -/* +/** + * A utility class for performing single-bit operations on integers. + * These operations include flipping, setting, clearing, and getting + * individual bits at specified positions. + * + * Bit positions are zero-indexed (i.e., the least significant bit is at position 0). + * These methods leverage bitwise operations for optimal performance. + * + * Examples: + * - `flipBit(3, 1)` flips the bit at index 1 in binary `11` (result: `1`). + * - `setBit(4, 0)` sets the bit at index 0 in `100` (result: `101` or 5). + * - `clearBit(7, 1)` clears the bit at index 1 in `111` (result: `101` or 5). + * - `getBit(6, 0)` checks if the least significant bit is set (result: `0`). + * + * Time Complexity: O(1) for all operations. + * * Author: lukasb1b (https://github.com/lukasb1b) */ - public final class SingleBitOperations { private SingleBitOperations() { } + /** - * Flip the bit at position 'bit' in 'num' + * Flips (toggles) the bit at the specified position. + * + * @param num the input number + * @param bit the position of the bit to flip (0-indexed) + * @return the new number after flipping the specified bit */ public static int flipBit(final int num, final int bit) { return num ^ (1 << bit); } + /** - * Set the bit at position 'bit' to 1 in the 'num' variable + * Sets the bit at the specified position to 1. + * + * @param num the input number + * @param bit the position of the bit to set (0-indexed) + * @return the new number after setting the specified bit to 1 */ public static int setBit(final int num, final int bit) { return num | (1 << bit); } + /** - * Clears the bit located at 'bit' from 'num' + * Clears the bit at the specified position (sets it to 0). + * + * @param num the input number + * @param bit the position of the bit to clear (0-indexed) + * @return the new number after clearing the specified bit */ public static int clearBit(final int num, final int bit) { return num & ~(1 << bit); } + /** - * Get the bit located at 'bit' from 'num' + * Gets the bit value (0 or 1) at the specified position. + * + * @param num the input number + * @param bit the position of the bit to retrieve (0-indexed) + * @return 1 if the bit is set, 0 otherwise */ public static int getBit(final int num, final int bit) { - return ((num >> bit) & 1); + return (num >> bit) & 1; } } diff --git a/src/main/java/com/thealgorithms/bitmanipulation/SingleElement.java b/src/main/java/com/thealgorithms/bitmanipulation/SingleElement.java new file mode 100644 index 000000000000..85ebdf02db25 --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/SingleElement.java @@ -0,0 +1,39 @@ +package com.thealgorithms.bitmanipulation; + +/** + * Utility class to find the single non-duplicate element from an array + * where all other elements appear twice. + *

+ * The algorithm runs in O(n) time complexity and O(1) space complexity + * using bitwise XOR. + *

+ * + * @author Tuhin M + */ +public final class SingleElement { + + /** + * Private constructor to prevent instantiation of this utility class. + * Throws an UnsupportedOperationException if attempted. + */ + private SingleElement() { + throw new UnsupportedOperationException("Utility Class"); + } + + /** + * Finds the single non-duplicate element in an array where every other + * element appears exactly twice. Uses bitwise XOR to achieve O(n) time + * complexity and O(1) space complexity. + * + * @param arr the input array containing integers where every element + * except one appears exactly twice + * @return the single non-duplicate element + */ + public static int findSingleElement(int[] arr) { + int ele = 0; + for (int i = 0; i < arr.length; i++) { + ele ^= arr[i]; + } + return ele; + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/SwapAdjacentBits.java b/src/main/java/com/thealgorithms/bitmanipulation/SwapAdjacentBits.java new file mode 100644 index 000000000000..98a7de8bdf1a --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/SwapAdjacentBits.java @@ -0,0 +1,57 @@ +package com.thealgorithms.bitmanipulation; + +/** + * A utility class to swap every pair of adjacent bits in a given integer. + * This operation shifts the even-positioned bits to odd positions and vice versa. + * + * Example: + * - Input: 2 (binary: `10`) → Output: 1 (binary: `01`) + * - Input: 43 (binary: `101011`) → Output: 23 (binary: `010111`) + * + * **Explanation of the Algorithm:** + * 1. Mask even-positioned bits: Using `0xAAAAAAAA` (binary: `101010...`), + * which selects bits in even positions. + * 2. Mask odd-positioned bits: Using `0x55555555` (binary: `010101...`), + * which selects bits in odd positions. + * 3. Shift bits: + * - Right-shift even-positioned bits by 1 to move them to odd positions. + * - Left-shift odd-positioned bits by 1 to move them to even positions. + * 4. Combine both shifted results using bitwise OR (`|`) to produce the final result. + * + * Use Case: This algorithm can be useful in applications involving low-level bit manipulation, + * such as encoding, data compression, or cryptographic transformations. + * + * Time Complexity: O(1) (constant time, since operations are bitwise). + * + * Author: Lakshyajeet Singh Goyal (https://github.com/DarkMatter-999) + */ +public final class SwapAdjacentBits { + private SwapAdjacentBits() { + } + + /** + * Swaps every pair of adjacent bits of a given integer. + * Steps: + * 1. Mask the even-positioned bits. + * 2. Mask the odd-positioned bits. + * 3. Shift the even bits to the right and the odd bits to the left. + * 4. Combine the shifted bits. + * + * @param num the integer whose bits are to be swapped + * @return the integer after swapping every pair of adjacent bits + */ + public static int swapAdjacentBits(int num) { + // mask the even bits (0xAAAAAAAA => 10101010...) + int evenBits = num & 0xAAAAAAAA; + + // mask the odd bits (0x55555555 => 01010101...) + int oddBits = num & 0x55555555; + + // right shift even bits and left shift odd bits + evenBits >>= 1; + oddBits <<= 1; + + // combine shifted bits + return evenBits | oddBits; + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/TwosComplement.java b/src/main/java/com/thealgorithms/bitmanipulation/TwosComplement.java new file mode 100644 index 000000000000..9b8cecd791a6 --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/TwosComplement.java @@ -0,0 +1,62 @@ +package com.thealgorithms.bitmanipulation; + +/** + * This class provides a method to compute the Two's Complement of a given binary number. + * + *

In two's complement representation, a binary number's negative value is obtained + * by taking the one's complement (inverting all bits) and then adding 1 to the result. + * This method handles both small and large binary strings and ensures the output is + * correct for all binary inputs, including edge cases like all zeroes and all ones. + * + *

For more information on Two's Complement: + * @see Wikipedia - Two's Complement + * + *

Algorithm originally suggested by Jon von Neumann. + * + * @author Abhinay Verma (https://github.com/Monk-AbhinayVerma) + */ +public final class TwosComplement { + private TwosComplement() { + } + + /** + * Computes the Two's Complement of the given binary string. + * Steps: + * 1. Compute the One's Complement (invert all bits). + * 2. Add 1 to the One's Complement to get the Two's Complement. + * 3. Iterate from the rightmost bit to the left, adding 1 and carrying over as needed. + * 4. If a carry is still present after the leftmost bit, prepend '1' to handle overflow. + * + * @param binary The binary number as a string (only '0' and '1' characters allowed). + * @return The two's complement of the input binary string as a new binary string. + * @throws IllegalArgumentException If the input contains non-binary characters. + */ + public static String twosComplement(String binary) { + if (!binary.matches("[01]+")) { + throw new IllegalArgumentException("Input must contain only '0' and '1'."); + } + + StringBuilder onesComplement = new StringBuilder(); + for (char bit : binary.toCharArray()) { + onesComplement.append(bit == '0' ? '1' : '0'); + } + + StringBuilder twosComplement = new StringBuilder(onesComplement); + boolean carry = true; + + for (int i = onesComplement.length() - 1; i >= 0 && carry; i--) { + if (onesComplement.charAt(i) == '1') { + twosComplement.setCharAt(i, '0'); + } else { + twosComplement.setCharAt(i, '1'); + carry = false; + } + } + + if (carry) { + twosComplement.insert(0, '1'); + } + + return twosComplement.toString(); + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/Xs3Conversion.java b/src/main/java/com/thealgorithms/bitmanipulation/Xs3Conversion.java new file mode 100644 index 000000000000..b22abc0c04ff --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/Xs3Conversion.java @@ -0,0 +1,58 @@ +package com.thealgorithms.bitmanipulation; + +/** + * This class provides methods to convert between XS-3 (Excess-3) and binary. + * + * Excess-3, also called XS-3, is a binary-coded decimal (BCD) code in which each decimal digit is represented by its corresponding 4-bit binary value plus 3. + * + * For more information, refer to the + * Excess-3 Wikipedia page. + * + * Example usage: + *

+ * int binary = Xs3Conversion.xs3ToBinary(0x4567);
+ * System.out.println("XS-3 0x4567 to binary: " + binary); // Output: 1234
+ *
+ * int xs3 = Xs3Conversion.binaryToXs3(1234);
+ * System.out.println("Binary 1234 to XS-3: " + Integer.toHexString(xs3)); // Output: 0x4567
+ * 
+ */ +public final class Xs3Conversion { + private Xs3Conversion() { + } + /** + * Converts an XS-3 (Excess-3) number to binary. + * + * @param xs3 The XS-3 number. + * @return The corresponding binary number. + */ + public static int xs3ToBinary(int xs3) { + int binary = 0; + int multiplier = 1; + while (xs3 > 0) { + int digit = (xs3 & 0xF) - 3; // Extract the last 4 bits (one XS-3 digit) and subtract 3 + binary += digit * multiplier; + multiplier *= 10; + xs3 >>= 4; // Shift right by 4 bits to process the next XS-3 digit + } + return binary; + } + + /** + * Converts a binary number to XS-3 (Excess-3). + * + * @param binary The binary number. + * @return The corresponding XS-3 number. + */ + public static int binaryToXs3(int binary) { + int xs3 = 0; + int shift = 0; + while (binary > 0) { + int digit = (binary % 10) + 3; // Extract the last decimal digit and add 3 + xs3 |= (digit << (shift * 4)); // Shift the digit to the correct XS-3 position + binary /= 10; // Remove the last decimal digit + shift++; + } + return xs3; + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/ADFGVXCipher.java b/src/main/java/com/thealgorithms/ciphers/ADFGVXCipher.java new file mode 100644 index 000000000000..d915858f9e6f --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/ADFGVXCipher.java @@ -0,0 +1,167 @@ +package com.thealgorithms.ciphers; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * The ADFGVX cipher is a fractionating transposition cipher that was used by + * the German Army during World War I. It combines a **Polybius square substitution** + * with a **columnar transposition** to enhance encryption strength. + *

+ * The name "ADFGVX" refers to the six letters (A, D, F, G, V, X) used as row and + * column labels in the Polybius square. This cipher was designed to secure + * communication and create complex, hard-to-break ciphertexts. + *

+ * Learn more: ADFGVX Cipher - Wikipedia. + *

+ * Example usage: + *

+ * ADFGVXCipher cipher = new ADFGVXCipher();
+ * String encrypted = cipher.encrypt("attack at 1200am", "PRIVACY");
+ * String decrypted = cipher.decrypt(encrypted, "PRIVACY");
+ * 
+ * + * @author bennybebo + */ +public class ADFGVXCipher { + + // Constants used in the Polybius square + private static final char[] POLYBIUS_LETTERS = {'A', 'D', 'F', 'G', 'V', 'X'}; + private static final char[][] POLYBIUS_SQUARE = {{'N', 'A', '1', 'C', '3', 'H'}, {'8', 'T', 'B', '2', 'O', 'M'}, {'E', '5', 'W', 'R', 'P', 'D'}, {'4', 'F', '6', 'G', '7', 'I'}, {'9', 'J', '0', 'K', 'L', 'Q'}, {'S', 'U', 'V', 'X', 'Y', 'Z'}}; + + // Maps for fast substitution lookups + private static final Map POLYBIUS_MAP = new HashMap<>(); + private static final Map REVERSE_POLYBIUS_MAP = new HashMap<>(); + + // Static block to initialize the lookup tables from the Polybius square + static { + for (int i = 0; i < POLYBIUS_SQUARE.length; i++) { + for (int j = 0; j < POLYBIUS_SQUARE[i].length; j++) { + String key = "" + POLYBIUS_LETTERS[i] + POLYBIUS_LETTERS[j]; + POLYBIUS_MAP.put(key, POLYBIUS_SQUARE[i][j]); + REVERSE_POLYBIUS_MAP.put(POLYBIUS_SQUARE[i][j], key); + } + } + } + + /** + * Encrypts a given plaintext using the ADFGVX cipher with the provided keyword. + * Steps: + * 1. Substitute each letter in the plaintext with a pair of ADFGVX letters. + * 2. Perform a columnar transposition on the fractionated text using the keyword. + * + * @param plaintext The message to be encrypted (can contain letters and digits). + * @param key The keyword for columnar transposition. + * @return The encrypted message as ciphertext. + */ + public String encrypt(String plaintext, String key) { + plaintext = plaintext.toUpperCase().replaceAll("[^A-Z0-9]", ""); // Sanitize input + StringBuilder fractionatedText = new StringBuilder(); + + for (char c : plaintext.toCharArray()) { + fractionatedText.append(REVERSE_POLYBIUS_MAP.get(c)); + } + + return columnarTransposition(fractionatedText.toString(), key); + } + + /** + * Decrypts a given ciphertext using the ADFGVX cipher with the provided keyword. + * Steps: + * 1. Reverse the columnar transposition performed during encryption. + * 2. Substitute each pair of ADFGVX letters with the corresponding plaintext letter. + * The resulting text is the decrypted message. + * + * @param ciphertext The encrypted message. + * @param key The keyword used during encryption. + * @return The decrypted plaintext message. + */ + public String decrypt(String ciphertext, String key) { + String fractionatedText = reverseColumnarTransposition(ciphertext, key); + + StringBuilder plaintext = new StringBuilder(); + for (int i = 0; i < fractionatedText.length(); i += 2) { + String pair = fractionatedText.substring(i, i + 2); + plaintext.append(POLYBIUS_MAP.get(pair)); + } + + return plaintext.toString(); + } + + /** + * Helper method: Performs columnar transposition during encryption + * + * @param text The fractionated text to be transposed + * @param key The keyword for columnar transposition + * @return The transposed text + */ + private String columnarTransposition(String text, String key) { + int numRows = (int) Math.ceil((double) text.length() / key.length()); + char[][] table = new char[numRows][key.length()]; + for (char[] row : table) { // Fill empty cells with underscores + Arrays.fill(row, '_'); + } + + // Populate the table row by row + for (int i = 0; i < text.length(); i++) { + table[i / key.length()][i % key.length()] = text.charAt(i); + } + + // Read columns based on the alphabetical order of the key + StringBuilder ciphertext = new StringBuilder(); + char[] sortedKey = key.toCharArray(); + Arrays.sort(sortedKey); + + for (char keyChar : sortedKey) { + int column = key.indexOf(keyChar); + for (char[] row : table) { + if (row[column] != '_') { + ciphertext.append(row[column]); + } + } + } + + return ciphertext.toString(); + } + + /** + * Helper method: Reverses the columnar transposition during decryption + * + * @param ciphertext The transposed text to be reversed + * @param key The keyword used during encryption + * @return The reversed text + */ + private String reverseColumnarTransposition(String ciphertext, String key) { + int numRows = (int) Math.ceil((double) ciphertext.length() / key.length()); + char[][] table = new char[numRows][key.length()]; + + char[] sortedKey = key.toCharArray(); + Arrays.sort(sortedKey); + + int index = 0; + // Populate the table column by column according to the sorted key + for (char keyChar : sortedKey) { + int column = key.indexOf(keyChar); + for (int row = 0; row < numRows; row++) { + if (index < ciphertext.length()) { + table[row][column] = ciphertext.charAt(index++); + } else { + table[row][column] = '_'; + } + } + } + + // Read the table row by row to reconstruct the fractionated text + StringBuilder fractionatedText = new StringBuilder(); + for (char[] row : table) { + for (char cell : row) { + if (cell != '_') { + fractionatedText.append(cell); + } + } + } + + return fractionatedText.toString(); + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/AES.java b/src/main/java/com/thealgorithms/ciphers/AES.java index 5d614afbe584..1c283f6b7655 100644 --- a/src/main/java/com/thealgorithms/ciphers/AES.java +++ b/src/main/java/com/thealgorithms/ciphers/AES.java @@ -2418,8 +2418,6 @@ public static BigInteger scheduleCore(BigInteger t, int rconCounter) { rBytes = new StringBuilder(rBytes.substring(0, i * 2) + currentByteBits + rBytes.substring((i + 1) * 2)); } - // t = new BigInteger(rBytes, 16); - // return t; return new BigInteger(rBytes.toString(), 16); } diff --git a/src/main/java/com/thealgorithms/ciphers/AffineCipher.java b/src/main/java/com/thealgorithms/ciphers/AffineCipher.java index 636323b63646..b82681372f2b 100644 --- a/src/main/java/com/thealgorithms/ciphers/AffineCipher.java +++ b/src/main/java/com/thealgorithms/ciphers/AffineCipher.java @@ -34,19 +34,19 @@ private AffineCipher() { */ static String encryptMessage(char[] msg) { // Cipher Text initially empty - String cipher = ""; + StringBuilder cipher = new StringBuilder(); for (int i = 0; i < msg.length; i++) { // Avoid space to be encrypted /* applying encryption formula ( a * x + b ) mod m {here x is msg[i] and m is 26} and added 'A' to bring it in the range of ASCII alphabet [65-90 | A-Z] */ if (msg[i] != ' ') { - cipher += (char) ((((a * (msg[i] - 'A')) + b) % 26) + 'A'); + cipher.append((char) ((((a * (msg[i] - 'A')) + b) % 26) + 'A')); } else { // else simply append space character - cipher += msg[i]; + cipher.append(msg[i]); } } - return cipher; + return cipher.toString(); } /** @@ -56,7 +56,7 @@ static String encryptMessage(char[] msg) { * @return the decrypted plaintext message */ static String decryptCipher(String cipher) { - String msg = ""; + StringBuilder msg = new StringBuilder(); int aInv = 0; int flag; @@ -75,13 +75,13 @@ static String decryptCipher(String cipher) { {here x is cipher[i] and m is 26} and added 'A' to bring it in the range of ASCII alphabet [65-90 | A-Z] */ if (cipher.charAt(i) != ' ') { - msg += (char) (((aInv * ((cipher.charAt(i) - 'A') - b + 26)) % 26) + 'A'); + msg.append((char) (((aInv * ((cipher.charAt(i) - 'A') - b + 26)) % 26) + 'A')); } else { // else simply append space character - msg += cipher.charAt(i); + msg.append(cipher.charAt(i)); } } - return msg; + return msg.toString(); } // Driver code diff --git a/src/main/java/com/thealgorithms/ciphers/AtbashCipher.java b/src/main/java/com/thealgorithms/ciphers/AtbashCipher.java new file mode 100644 index 000000000000..9169aa82bd75 --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/AtbashCipher.java @@ -0,0 +1,101 @@ +package com.thealgorithms.ciphers; + +/** + * The Atbash cipher is a classic substitution cipher that substitutes each letter + * with its opposite letter in the alphabet. + * + * For example: + * - 'A' becomes 'Z', 'B' becomes 'Y', 'C' becomes 'X', and so on. + * - Similarly, 'a' becomes 'z', 'b' becomes 'y', and so on. + * + * The cipher works identically for both uppercase and lowercase letters. + * Non-alphabetical characters remain unchanged in the output. + * + * This cipher is symmetric, meaning that applying the cipher twice will return + * the original text. Therefore, the same function is used for both encryption and decryption. + * + *

Usage Example:

+ *
+ * AtbashCipher cipher = new AtbashCipher("Hello World!");
+ * String encrypted = cipher.convert(); // Output: "Svool Dliow!"
+ * 
+ * + * @author Krounosity + * @see Atbash Cipher (Wikipedia) + */ +public class AtbashCipher { + + private String toConvert; + + public AtbashCipher() { + } + + /** + * Constructor with a string parameter. + * + * @param str The string to be converted using the Atbash cipher + */ + public AtbashCipher(String str) { + this.toConvert = str; + } + + /** + * Returns the current string set for conversion. + * + * @return The string to be converted + */ + public String getString() { + return toConvert; + } + + /** + * Sets the string to be converted using the Atbash cipher. + * + * @param str The new string to convert + */ + public void setString(String str) { + this.toConvert = str; + } + + /** + * Checks if a character is uppercase. + * + * @param ch The character to check + * @return {@code true} if the character is uppercase, {@code false} otherwise + */ + private boolean isCapital(char ch) { + return ch >= 'A' && ch <= 'Z'; + } + + /** + * Checks if a character is lowercase. + * + * @param ch The character to check + * @return {@code true} if the character is lowercase, {@code false} otherwise + */ + private boolean isSmall(char ch) { + return ch >= 'a' && ch <= 'z'; + } + + /** + * Converts the input string using the Atbash cipher. + * Alphabetic characters are substituted with their opposite in the alphabet, + * while non-alphabetic characters remain unchanged. + * + * @return The converted string after applying the Atbash cipher + */ + public String convert() { + StringBuilder convertedString = new StringBuilder(); + + for (char ch : toConvert.toCharArray()) { + if (isSmall(ch)) { + convertedString.append((char) ('z' - (ch - 'a'))); + } else if (isCapital(ch)) { + convertedString.append((char) ('Z' - (ch - 'A'))); + } else { + convertedString.append(ch); + } + } + return convertedString.toString(); + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/BaconianCipher.java b/src/main/java/com/thealgorithms/ciphers/BaconianCipher.java new file mode 100644 index 000000000000..16dfd6e674af --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/BaconianCipher.java @@ -0,0 +1,71 @@ +package com.thealgorithms.ciphers; + +import java.util.HashMap; +import java.util.Map; + +/** + * The Baconian Cipher is a substitution cipher where each letter is represented + * by a group of five binary digits (A's and B's). It can also be used to hide + * messages within other texts, making it a simple form of steganography. + * https://en.wikipedia.org/wiki/Bacon%27s_cipher + * + * @author Bennybebo + */ +public class BaconianCipher { + + private static final Map BACONIAN_MAP = new HashMap<>(); + private static final Map REVERSE_BACONIAN_MAP = new HashMap<>(); + + static { + // Initialize the Baconian cipher mappings + String[] baconianAlphabet = {"AAAAA", "AAAAB", "AAABA", "AAABB", "AABAA", "AABAB", "AABBA", "AABBB", "ABAAA", "ABAAB", "ABABA", "ABABB", "ABBAA", "ABBAB", "ABBBA", "ABBBB", "BAAAA", "BAAAB", "BAABA", "BAABB", "BABAA", "BABAB", "BABBA", "BABBB", "BBAAA", "BBAAB"}; + char letter = 'A'; + for (String code : baconianAlphabet) { + BACONIAN_MAP.put(letter, code); + REVERSE_BACONIAN_MAP.put(code, letter); + letter++; + } + + // Handle I/J as the same letter + BACONIAN_MAP.put('I', BACONIAN_MAP.get('J')); + REVERSE_BACONIAN_MAP.put(BACONIAN_MAP.get('I'), 'I'); + } + + /** + * Encrypts the given plaintext using the Baconian cipher. + * + * @param plaintext The plaintext message to encrypt. + * @return The ciphertext as a binary (A/B) sequence. + */ + public String encrypt(String plaintext) { + StringBuilder ciphertext = new StringBuilder(); + plaintext = plaintext.toUpperCase().replaceAll("[^A-Z]", ""); // Remove non-letter characters + + for (char letter : plaintext.toCharArray()) { + ciphertext.append(BACONIAN_MAP.get(letter)); + } + + return ciphertext.toString(); + } + + /** + * Decrypts the given ciphertext encoded in binary (A/B) format using the Baconian cipher. + * + * @param ciphertext The ciphertext to decrypt. + * @return The decrypted plaintext message. + */ + public String decrypt(String ciphertext) { + StringBuilder plaintext = new StringBuilder(); + + for (int i = 0; i < ciphertext.length(); i += 5) { + String code = ciphertext.substring(i, i + 5); + if (REVERSE_BACONIAN_MAP.containsKey(code)) { + plaintext.append(REVERSE_BACONIAN_MAP.get(code)); + } else { + throw new IllegalArgumentException("Invalid Baconian code: " + code); + } + } + + return plaintext.toString(); + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/Blowfish.java b/src/main/java/com/thealgorithms/ciphers/Blowfish.java index f6a0a3753e9b..ea1807e62710 100644 --- a/src/main/java/com/thealgorithms/ciphers/Blowfish.java +++ b/src/main/java/com/thealgorithms/ciphers/Blowfish.java @@ -1078,7 +1078,7 @@ public class Blowfish { * @return String object which is a binary representation of the hex number passed as parameter */ private String hexToBin(String hex) { - String binary = ""; + StringBuilder binary = new StringBuilder(); long num; String binary4B; int n = hex.length(); @@ -1089,9 +1089,9 @@ private String hexToBin(String hex) { binary4B = "0000" + binary4B; binary4B = binary4B.substring(binary4B.length() - 4); - binary += binary4B; + binary.append(binary4B); } - return binary; + return binary.toString(); } /** @@ -1103,12 +1103,12 @@ private String hexToBin(String hex) { */ private String binToHex(String binary) { long num = Long.parseUnsignedLong(binary, 2); - String hex = Long.toHexString(num); + StringBuilder hex = new StringBuilder(Long.toHexString(num)); while (hex.length() < (binary.length() / 4)) { - hex = "0" + hex; + hex.insert(0, "0"); } - return hex; + return hex.toString(); } /** @@ -1121,12 +1121,12 @@ private String binToHex(String binary) { private String xor(String a, String b) { a = hexToBin(a); b = hexToBin(b); - String ans = ""; + StringBuilder ans = new StringBuilder(); for (int i = 0; i < a.length(); i++) { - ans += (char) (((a.charAt(i) - '0') ^ (b.charAt(i) - '0')) + '0'); + ans.append((char) (((a.charAt(i) - '0') ^ (b.charAt(i) - '0')) + '0')); } - ans = binToHex(ans); - return ans; + ans = new StringBuilder(binToHex(ans.toString())); + return ans.toString(); } /** diff --git a/src/main/java/com/thealgorithms/ciphers/Caesar.java b/src/main/java/com/thealgorithms/ciphers/Caesar.java index 61c444cf6463..23535bc2b5d2 100644 --- a/src/main/java/com/thealgorithms/ciphers/Caesar.java +++ b/src/main/java/com/thealgorithms/ciphers/Caesar.java @@ -9,6 +9,9 @@ * @author khalil2535 */ public class Caesar { + private static char normalizeShift(final int shift) { + return (char) (shift % 26); + } /** * Encrypt text by shifting every Latin char by add number shift for ASCII @@ -19,7 +22,7 @@ public class Caesar { public String encode(String message, int shift) { StringBuilder encoded = new StringBuilder(); - shift %= 26; + final char shiftChar = normalizeShift(shift); final int length = message.length(); for (int i = 0; i < length; i++) { @@ -29,10 +32,10 @@ public String encode(String message, int shift) { char current = message.charAt(i); // Java law : char + int = char if (isCapitalLatinLetter(current)) { - current += shift; + current += shiftChar; encoded.append((char) (current > 'Z' ? current - 26 : current)); // 26 = number of latin letters } else if (isSmallLatinLetter(current)) { - current += shift; + current += shiftChar; encoded.append((char) (current > 'z' ? current - 26 : current)); // 26 = number of latin letters } else { encoded.append(current); @@ -50,16 +53,16 @@ public String encode(String message, int shift) { public String decode(String encryptedMessage, int shift) { StringBuilder decoded = new StringBuilder(); - shift %= 26; + final char shiftChar = normalizeShift(shift); final int length = encryptedMessage.length(); for (int i = 0; i < length; i++) { char current = encryptedMessage.charAt(i); if (isCapitalLatinLetter(current)) { - current -= shift; + current -= shiftChar; decoded.append((char) (current < 'A' ? current + 26 : current)); // 26 = number of latin letters } else if (isSmallLatinLetter(current)) { - current -= shift; + current -= shiftChar; decoded.append((char) (current < 'a' ? current + 26 : current)); // 26 = number of latin letters } else { decoded.append(current); diff --git a/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java b/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java index d7e64a12ebfd..b6b889b079ca 100644 --- a/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java +++ b/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java @@ -27,13 +27,13 @@ private ColumnarTranspositionCipher() { * @return a String with the word encrypted by the Columnar Transposition * Cipher Rule */ - public static String encrpyter(String word, String keyword) { + public static String encrypt(final String word, final String keyword) { ColumnarTranspositionCipher.keyword = keyword; - abecedariumBuilder(500); + abecedariumBuilder(); table = tableBuilder(word); Object[][] sortedTable = sortTable(table); StringBuilder wordEncrypted = new StringBuilder(); - for (int i = 0; i < sortedTable[i].length; i++) { + for (int i = 0; i < sortedTable[0].length; i++) { for (int j = 1; j < sortedTable.length; j++) { wordEncrypted.append(sortedTable[j][i]); } @@ -51,11 +51,12 @@ public static String encrpyter(String word, String keyword) { * @return a String with the word encrypted by the Columnar Transposition * Cipher Rule */ - public static String encrpyter(String word, String keyword, String abecedarium) { + public static String encrypt(String word, String keyword, String abecedarium) { ColumnarTranspositionCipher.keyword = keyword; ColumnarTranspositionCipher.abecedarium = Objects.requireNonNullElse(abecedarium, ABECEDARIUM); table = tableBuilder(word); Object[][] sortedTable = sortTable(table); + StringBuilder wordEncrypted = new StringBuilder(); for (int i = 0; i < sortedTable[0].length; i++) { for (int j = 1; j < sortedTable.length; j++) { @@ -72,7 +73,7 @@ public static String encrpyter(String word, String keyword, String abecedarium) * @return a String decrypted with the word encrypted by the Columnar * Transposition Cipher Rule */ - public static String decrypter() { + public static String decrypt() { StringBuilder wordDecrypted = new StringBuilder(); for (int i = 1; i < table.length; i++) { for (Object item : table[i]) { @@ -91,14 +92,14 @@ public static String decrypter() { */ private static Object[][] tableBuilder(String word) { Object[][] table = new Object[numberOfRows(word) + 1][keyword.length()]; - char[] wordInChards = word.toCharArray(); - // Fils in the respective numbers + char[] wordInChars = word.toCharArray(); + // Fills in the respective numbers for the column table[0] = findElements(); int charElement = 0; for (int i = 1; i < table.length; i++) { for (int j = 0; j < table[i].length; j++) { - if (charElement < wordInChards.length) { - table[i][j] = wordInChards[charElement]; + if (charElement < wordInChars.length) { + table[i][j] = wordInChars[charElement]; charElement++; } else { table[i][j] = ENCRYPTION_FIELD_CHAR; @@ -116,7 +117,7 @@ private static Object[][] tableBuilder(String word) { * order to respect the Columnar Transposition Cipher Rule. */ private static int numberOfRows(String word) { - if (word.length() / keyword.length() > word.length() / keyword.length()) { + if (word.length() % keyword.length() != 0) { return (word.length() / keyword.length()) + 1; } else { return word.length() / keyword.length(); @@ -173,13 +174,11 @@ private static void switchColumns(Object[][] table, int firstColumnIndex, int se } /** - * Creates an abecedarium with a specified ascii inded - * - * @param value Number of characters being used based on the ASCII Table + * Creates an abecedarium with all available ascii values. */ - private static void abecedariumBuilder(int value) { + private static void abecedariumBuilder() { StringBuilder t = new StringBuilder(); - for (int i = 0; i < value; i++) { + for (int i = 0; i < 256; i++) { t.append((char) i); } abecedarium = t.toString(); diff --git a/src/main/java/com/thealgorithms/ciphers/DiffieHellman.java b/src/main/java/com/thealgorithms/ciphers/DiffieHellman.java new file mode 100644 index 000000000000..7470b40e001a --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/DiffieHellman.java @@ -0,0 +1,36 @@ +package com.thealgorithms.ciphers; + +import java.math.BigInteger; + +public final class DiffieHellman { + + private final BigInteger base; + private final BigInteger secret; + private final BigInteger prime; + + // Constructor to initialize base, secret, and prime + public DiffieHellman(BigInteger base, BigInteger secret, BigInteger prime) { + // Check for non-null and positive values + if (base == null || secret == null || prime == null || base.signum() <= 0 || secret.signum() <= 0 || prime.signum() <= 0) { + throw new IllegalArgumentException("Base, secret, and prime must be non-null and positive values."); + } + this.base = base; + this.secret = secret; + this.prime = prime; + } + + // Method to calculate public value (g^x mod p) + public BigInteger calculatePublicValue() { + // Returns g^x mod p + return base.modPow(secret, prime); + } + + // Method to calculate the shared secret key (otherPublic^secret mod p) + public BigInteger calculateSharedSecret(BigInteger otherPublicValue) { + if (otherPublicValue == null || otherPublicValue.signum() <= 0) { + throw new IllegalArgumentException("Other public value must be non-null and positive."); + } + // Returns b^x mod p or a^y mod p + return otherPublicValue.modPow(secret, prime); + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/ECC.java b/src/main/java/com/thealgorithms/ciphers/ECC.java new file mode 100644 index 000000000000..7b1e37f0e1e1 --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/ECC.java @@ -0,0 +1,236 @@ +package com.thealgorithms.ciphers; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * ECC - Elliptic Curve Cryptography + * Elliptic Curve Cryptography is a public-key cryptography method that uses the algebraic structure of + * elliptic curves over finite fields. ECC provides a higher level of security with smaller key sizes compared + * to other public-key methods like RSA, making it particularly suitable for environments where computational + * resources are limited, such as mobile devices and embedded systems. + * + * This class implements elliptic curve cryptography, providing encryption and decryption + * functionalities based on public and private key pairs. + * + * @author xuyang + */ +public class ECC { + + private BigInteger privateKey; // Private key used for decryption + private ECPoint publicKey; // Public key used for encryption + private EllipticCurve curve; // Elliptic curve used in cryptography + private ECPoint basePoint; // Base point G on the elliptic curve + + public ECC(int bits) { + generateKeys(bits); // Generates public-private key pair + } + + public EllipticCurve getCurve() { + return curve; // Returns the elliptic curve + } + + public void setCurve(EllipticCurve curve) { + this.curve = curve; + } + + // Getter and Setter for private key + public BigInteger getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(BigInteger privateKey) { + this.privateKey = privateKey; + } + + /** + * Encrypts the message using the public key. + * The message is transformed into an ECPoint and encrypted with elliptic curve operations. + * + * @param message The plain message to be encrypted + * @return The encrypted message as an array of ECPoints (R, S) + */ + public ECPoint[] encrypt(String message) { + BigInteger m = new BigInteger(message.getBytes()); // Convert message to BigInteger + SecureRandom r = new SecureRandom(); // Generate random value for k + BigInteger k = new BigInteger(curve.getFieldSize(), r); // Generate random scalar k + + // Calculate point r = k * G, where G is the base point + ECPoint rPoint = basePoint.multiply(k, curve.getP(), curve.getA()); + + // Calculate point s = k * publicKey + encodedMessage + ECPoint sPoint = publicKey.multiply(k, curve.getP(), curve.getA()).add(curve.encodeMessage(m), curve.getP(), curve.getA()); + + return new ECPoint[] {rPoint, sPoint}; // Return encrypted message as two ECPoints + } + + /** + * Decrypts the encrypted message using the private key. + * The decryption process is the reverse of encryption, recovering the original message. + * + * @param encryptedMessage The encrypted message as an array of ECPoints (R, S) + * @return The decrypted plain message as a String + */ + public String decrypt(ECPoint[] encryptedMessage) { + ECPoint rPoint = encryptedMessage[0]; // First part of ciphertext + ECPoint sPoint = encryptedMessage[1]; // Second part of ciphertext + + // Perform decryption: s - r * privateKey + ECPoint decodedMessage = sPoint.subtract(rPoint.multiply(privateKey, curve.getP(), curve.getA()), curve.getP(), curve.getA()); + + BigInteger m = curve.decodeMessage(decodedMessage); // Decode the message from ECPoint + + return new String(m.toByteArray()); // Convert BigInteger back to String + } + + /** + * Generates a new public-private key pair for encryption and decryption. + * + * @param bits The size (in bits) of the keys to generate + */ + public final void generateKeys(int bits) { + SecureRandom r = new SecureRandom(); + curve = new EllipticCurve(bits); // Initialize a new elliptic curve + basePoint = curve.getBasePoint(); // Set the base point G + + // Generate private key as a random BigInteger + privateKey = new BigInteger(bits, r); + + // Generate public key as the point publicKey = privateKey * G + publicKey = basePoint.multiply(privateKey, curve.getP(), curve.getA()); + } + + /** + * Class representing an elliptic curve with the form y^2 = x^3 + ax + b. + */ + public static class EllipticCurve { + private final BigInteger a; // Coefficient a in the curve equation + private final BigInteger b; // Coefficient b in the curve equation + private final BigInteger p; // Prime number p, defining the finite field + private final ECPoint basePoint; // Base point G on the curve + + // Constructor with explicit parameters for a, b, p, and base point + public EllipticCurve(BigInteger a, BigInteger b, BigInteger p, ECPoint basePoint) { + this.a = a; + this.b = b; + this.p = p; + this.basePoint = basePoint; + } + + // Constructor that randomly generates the curve parameters + public EllipticCurve(int bits) { + SecureRandom r = new SecureRandom(); + this.p = BigInteger.probablePrime(bits, r); // Random prime p + this.a = new BigInteger(bits, r); // Random coefficient a + this.b = new BigInteger(bits, r); // Random coefficient b + this.basePoint = new ECPoint(BigInteger.valueOf(4), BigInteger.valueOf(8)); // Fixed base point G + } + + public ECPoint getBasePoint() { + return basePoint; + } + + public BigInteger getP() { + return p; + } + + public BigInteger getA() { + return a; + } + + public BigInteger getB() { + return b; + } + + public int getFieldSize() { + return p.bitLength(); + } + + public ECPoint encodeMessage(BigInteger message) { + // Simple encoding of a message as an ECPoint (this is a simplified example) + return new ECPoint(message, message); + } + + public BigInteger decodeMessage(ECPoint point) { + return point.getX(); // Decode the message from ECPoint (simplified) + } + } + + /** + * Class representing a point on the elliptic curve. + */ + public static class ECPoint { + private final BigInteger x; // X-coordinate of the point + private final BigInteger y; // Y-coordinate of the point + + public ECPoint(BigInteger x, BigInteger y) { + this.x = x; + this.y = y; + } + + public BigInteger getX() { + return x; + } + + public BigInteger getY() { + return y; + } + + @Override + public String toString() { + return "ECPoint(x=" + x.toString() + ", y=" + y.toString() + ")"; + } + + /** + * Add two points on the elliptic curve. + */ + public ECPoint add(ECPoint other, BigInteger p, BigInteger a) { + if (this.x.equals(BigInteger.ZERO) && this.y.equals(BigInteger.ZERO)) { + return other; // If this point is the identity, return the other point + } + if (other.x.equals(BigInteger.ZERO) && other.y.equals(BigInteger.ZERO)) { + return this; // If the other point is the identity, return this point + } + + BigInteger lambda; + if (this.equals(other)) { + // Special case: point doubling + lambda = this.x.pow(2).multiply(BigInteger.valueOf(3)).add(a).multiply(this.y.multiply(BigInteger.valueOf(2)).modInverse(p)).mod(p); + } else { + // General case: adding two different points + lambda = other.y.subtract(this.y).multiply(other.x.subtract(this.x).modInverse(p)).mod(p); + } + + BigInteger xr = lambda.pow(2).subtract(this.x).subtract(other.x).mod(p); + BigInteger yr = lambda.multiply(this.x.subtract(xr)).subtract(this.y).mod(p); + + return new ECPoint(xr, yr); + } + + /** + * Subtract two points on the elliptic curve. + */ + public ECPoint subtract(ECPoint other, BigInteger p, BigInteger a) { + ECPoint negOther = new ECPoint(other.x, p.subtract(other.y)); // Negate the Y coordinate + return this.add(negOther, p, a); // Add the negated point + } + + /** + * Multiply a point by a scalar (repeated addition). + */ + public ECPoint multiply(BigInteger k, BigInteger p, BigInteger a) { + ECPoint result = new ECPoint(BigInteger.ZERO, BigInteger.ZERO); // Identity point + ECPoint addend = this; + + while (k.signum() > 0) { + if (k.testBit(0)) { + result = result.add(addend, p, a); // Add the current point + } + addend = addend.add(addend, p, a); // Double the point + k = k.shiftRight(1); // Divide k by 2 + } + + return result; + } + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/MonoAlphabetic.java b/src/main/java/com/thealgorithms/ciphers/MonoAlphabetic.java new file mode 100644 index 000000000000..1d5b7110a6f3 --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/MonoAlphabetic.java @@ -0,0 +1,48 @@ +package com.thealgorithms.ciphers; + +public final class MonoAlphabetic { + + private MonoAlphabetic() { + throw new UnsupportedOperationException("Utility class"); + } + + // Encryption method + public static String encrypt(String data, String key) { + if (!data.matches("[A-Z]+")) { + throw new IllegalArgumentException("Input data contains invalid characters. Only uppercase A-Z are allowed."); + } + StringBuilder sb = new StringBuilder(); + + // Encrypt each character + for (char c : data.toCharArray()) { + int idx = charToPos(c); // Get the index of the character + sb.append(key.charAt(idx)); // Map to the corresponding character in the key + } + return sb.toString(); + } + + // Decryption method + public static String decrypt(String data, String key) { + StringBuilder sb = new StringBuilder(); + + // Decrypt each character + for (char c : data.toCharArray()) { + int idx = key.indexOf(c); // Find the index of the character in the key + if (idx == -1) { + throw new IllegalArgumentException("Input data contains invalid characters."); + } + sb.append(posToChar(idx)); // Convert the index back to the original character + } + return sb.toString(); + } + + // Helper method: Convert a character to its position in the alphabet + private static int charToPos(char c) { + return c - 'A'; // Subtract 'A' to get position (0 for A, 1 for B, etc.) + } + + // Helper method: Convert a position in the alphabet to a character + private static char posToChar(int pos) { + return (char) (pos + 'A'); // Add 'A' to convert position back to character + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/RSA.java b/src/main/java/com/thealgorithms/ciphers/RSA.java index f50e501e68c8..28af1a62032a 100644 --- a/src/main/java/com/thealgorithms/ciphers/RSA.java +++ b/src/main/java/com/thealgorithms/ciphers/RSA.java @@ -4,7 +4,27 @@ import java.security.SecureRandom; /** - * @author Nguyen Duy Tiep on 23-Oct-17. + * RSA is an asymmetric cryptographic algorithm used for secure data encryption and decryption. + * It relies on a pair of keys: a public key (used for encryption) and a private key + * (used for decryption). The algorithm is based on the difficulty of factoring large prime numbers. + * + * This implementation includes key generation, encryption, and decryption methods that can handle both + * text-based messages and BigInteger inputs. For more details on RSA: + * RSA Cryptosystem - Wikipedia. + * + * Example Usage: + *
+ * RSA rsa = new RSA(1024);
+ * String encryptedMessage = rsa.encrypt("Hello RSA!");
+ * String decryptedMessage = rsa.decrypt(encryptedMessage);
+ * System.out.println(decryptedMessage);  // Output: Hello RSA!
+ * 
+ * + * Note: The key size directly affects the security and performance of the RSA algorithm. + * Larger keys are more secure but slower to compute. + * + * @author Nguyen Duy Tiep + * @version 23-Oct-17 */ public class RSA { @@ -12,55 +32,88 @@ public class RSA { private BigInteger privateKey; private BigInteger publicKey; + /** + * Constructor that generates RSA keys with the specified number of bits. + * + * @param bits The bit length of the keys to be generated. Common sizes include 512, 1024, 2048, etc. + */ public RSA(int bits) { generateKeys(bits); } /** - * @return encrypted message + * Encrypts a text message using the RSA public key. + * + * @param message The plaintext message to be encrypted. + * @throws IllegalArgumentException If the message is empty. + * @return The encrypted message represented as a String. */ public synchronized String encrypt(String message) { + if (message.isEmpty()) { + throw new IllegalArgumentException("Message is empty"); + } return (new BigInteger(message.getBytes())).modPow(publicKey, modulus).toString(); } /** - * @return encrypted message as big integer + * Encrypts a BigInteger message using the RSA public key. + * + * @param message The plaintext message as a BigInteger. + * @return The encrypted message as a BigInteger. */ public synchronized BigInteger encrypt(BigInteger message) { return message.modPow(publicKey, modulus); } /** - * @return plain message + * Decrypts an encrypted message (as String) using the RSA private key. + * + * @param encryptedMessage The encrypted message to be decrypted, represented as a String. + * @throws IllegalArgumentException If the message is empty. + * @return The decrypted plaintext message as a String. */ public synchronized String decrypt(String encryptedMessage) { + if (encryptedMessage.isEmpty()) { + throw new IllegalArgumentException("Message is empty"); + } return new String((new BigInteger(encryptedMessage)).modPow(privateKey, modulus).toByteArray()); } /** - * @return plain message as big integer + * Decrypts an encrypted BigInteger message using the RSA private key. + * + * @param encryptedMessage The encrypted message as a BigInteger. + * @return The decrypted plaintext message as a BigInteger. */ public synchronized BigInteger decrypt(BigInteger encryptedMessage) { return encryptedMessage.modPow(privateKey, modulus); } /** - * Generate a new public and private key set. + * Generates a new RSA key pair (public and private keys) with the specified bit length. + * Steps: + * 1. Generate two large prime numbers p and q. + * 2. Compute the modulus n = p * q. + * 3. Compute Euler's totient function: φ(n) = (p-1) * (q-1). + * 4. Choose a public key e (starting from 3) that is coprime with φ(n). + * 5. Compute the private key d as the modular inverse of e mod φ(n). + * The public key is (e, n) and the private key is (d, n). + * + * @param bits The bit length of the keys to be generated. */ public final synchronized void generateKeys(int bits) { - SecureRandom r = new SecureRandom(); - BigInteger p = new BigInteger(bits / 2, 100, r); - BigInteger q = new BigInteger(bits / 2, 100, r); + SecureRandom random = new SecureRandom(); + BigInteger p = new BigInteger(bits / 2, 100, random); + BigInteger q = new BigInteger(bits / 2, 100, random); modulus = p.multiply(q); - BigInteger m = (p.subtract(BigInteger.ONE)).multiply(q.subtract(BigInteger.ONE)); + BigInteger phi = (p.subtract(BigInteger.ONE)).multiply(q.subtract(BigInteger.ONE)); publicKey = BigInteger.valueOf(3L); - - while (m.gcd(publicKey).intValue() > 1) { + while (phi.gcd(publicKey).intValue() > 1) { publicKey = publicKey.add(BigInteger.TWO); } - privateKey = publicKey.modInverse(m); + privateKey = publicKey.modInverse(phi); } } diff --git a/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java b/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java new file mode 100644 index 000000000000..f81252980468 --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java @@ -0,0 +1,147 @@ +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. + * https://en.wikipedia.org/wiki/Rail_fence_cipher + * @author https://github.com/Krounosity + */ + +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) { + + // 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 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'). + for (int i = 0; i < rails; i++) { + Arrays.fill(strRail[i], '\n'); + } + + int row = 0; // Start at the first row + int col = 0; // Start at the first column + + int i = 0; + + // Fill the rail matrix with characters from the string based on the rail pattern. + while (col < str.length()) { + // Change direction to down when at the first row. + if (row == 0) { + down = true; + } + // Change direction to up when at the last row. + 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. + // 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); + } + } + } + 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) { + + // 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 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()]; + + int row = 0; // Start at the first row + int col = 0; // Start at the first column + + // Mark the pattern on the rail matrix using '*'. + while (col < str.length()) { + // Change direction to down when at the first row. + if (row == 0) { + down = true; + } + // Change direction to up when at the last row. + else if (row == rails - 1) { + down = false; + } + + // Mark the current position in the rail matrix. + strRail[row][col] = '*'; + col++; // Move to the next column. + // Move to the next row based on the direction. + if (down) { + row++; + } else { + row--; + } + } + + int index = 0; // Index to track characters from the input string. + // Fill the rail matrix with characters from the input string based on the marked pattern. + for (int i = 0; i < rails; i++) { + for (int j = 0; j < str.length(); j++) { + if (strRail[i][j] == '*') { + strRail[i][j] = str.charAt(index++); + } + } + } + + // Construct the decrypted string by following the zigzag pattern. + StringBuilder decryptedString = new StringBuilder(); + row = 0; // Reset to the first row + col = 0; // Reset to the first column + + while (col < str.length()) { + // Change direction to down when at the first row. + if (row == 0) { + down = true; + } + // Change direction to up when at the last row. + else if (row == rails - 1) { + down = false; + } + // Append the character from the rail matrix to the decrypted string. + decryptedString.append(strRail[row][col]); + col++; // Move to the next column. + // Move to the next row based on the direction. + if (down) { + row++; + } else { + row--; + } + } + + return decryptedString.toString(); + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/Vigenere.java b/src/main/java/com/thealgorithms/ciphers/Vigenere.java index 1702f1abb94c..0f117853bb85 100644 --- a/src/main/java/com/thealgorithms/ciphers/Vigenere.java +++ b/src/main/java/com/thealgorithms/ciphers/Vigenere.java @@ -1,16 +1,54 @@ package com.thealgorithms.ciphers; /** - * A Java implementation of Vigenere Cipher. + * A Java implementation of the Vigenère Cipher. + * + * The Vigenère Cipher is a polyalphabetic substitution cipher that uses a + * keyword to shift letters in the plaintext by different amounts, depending + * on the corresponding character in the keyword. It wraps around the alphabet, + * ensuring the shifts are within 'A'-'Z' or 'a'-'z'. + * + * Non-alphabetic characters (like spaces, punctuation) are kept unchanged. + * + * Encryption Example: + * - Plaintext: "Hello World!" + * - Key: "suchsecret" + * - Encrypted Text: "Zynsg Yfvev!" + * + * Decryption Example: + * - Ciphertext: "Zynsg Yfvev!" + * - Key: "suchsecret" + * - Decrypted Text: "Hello World!" + * + * Wikipedia Reference: + * Vigenère Cipher - Wikipedia * * @author straiffix * @author beingmartinbmc */ public class Vigenere { + /** + * Encrypts a given message using the Vigenère Cipher with the specified key. + * Steps: + * 1. Iterate over each character in the message. + * 2. If the character is a letter, shift it by the corresponding character in the key. + * 3. Preserve the case of the letter. + * 4. Preserve non-alphabetic characters. + * 5. Move to the next character in the key (cyclic). + * 6. Return the encrypted message. + * + * @param message The plaintext message to encrypt. + * @param key The keyword used for encryption. + * @throws IllegalArgumentException if the key is empty. + * @return The encrypted message. + */ public String encrypt(final String message, final String key) { - StringBuilder result = new StringBuilder(); + if (key.isEmpty()) { + throw new IllegalArgumentException("Key cannot be empty."); + } + StringBuilder result = new StringBuilder(); int j = 0; for (int i = 0; i < message.length(); i++) { char c = message.charAt(i); @@ -20,17 +58,35 @@ public String encrypt(final String message, final String key) { } else { result.append((char) ((c + key.toLowerCase().charAt(j) - 2 * 'a') % 26 + 'a')); } + j = ++j % key.length(); } else { result.append(c); } - j = ++j % key.length(); } return result.toString(); } + /** + * Decrypts a given message encrypted with the Vigenère Cipher using the specified key. + * Steps: + * 1. Iterate over each character in the message. + * 2. If the character is a letter, shift it back by the corresponding character in the key. + * 3. Preserve the case of the letter. + * 4. Preserve non-alphabetic characters. + * 5. Move to the next character in the key (cyclic). + * 6. Return the decrypted message. + * + * @param message The encrypted message to decrypt. + * @param key The keyword used for decryption. + * @throws IllegalArgumentException if the key is empty. + * @return The decrypted plaintext message. + */ public String decrypt(final String message, final String key) { - StringBuilder result = new StringBuilder(); + if (key.isEmpty()) { + throw new IllegalArgumentException("Key cannot be empty."); + } + StringBuilder result = new StringBuilder(); int j = 0; for (int i = 0; i < message.length(); i++) { char c = message.charAt(i); @@ -40,11 +96,10 @@ public String decrypt(final String message, final String key) { } else { result.append((char) ('z' - (25 - (c - key.toLowerCase().charAt(j))) % 26)); } + j = ++j % key.length(); } else { result.append(c); } - - j = ++j % key.length(); } return result.toString(); } diff --git a/src/main/java/com/thealgorithms/ciphers/XORCipher.java b/src/main/java/com/thealgorithms/ciphers/XORCipher.java index c4410d8c77ba..a612ccfbcdef 100644 --- a/src/main/java/com/thealgorithms/ciphers/XORCipher.java +++ b/src/main/java/com/thealgorithms/ciphers/XORCipher.java @@ -5,18 +5,46 @@ import java.util.HexFormat; /** - * A simple implementation of XOR cipher that, given a key, allows to encrypt and decrypt a plaintext. + * A simple implementation of the XOR cipher that allows both encryption and decryption + * using a given key. This cipher works by applying the XOR bitwise operation between + * the bytes of the input text and the corresponding bytes of the key (repeating the key + * if necessary). * - * @author lcsjunior + * Usage: + * - Encryption: Converts plaintext into a hexadecimal-encoded ciphertext. + * - Decryption: Converts the hexadecimal ciphertext back into plaintext. + * + * Characteristics: + * - Symmetric: The same key is used for both encryption and decryption. + * - Simple but vulnerable: XOR encryption is insecure for real-world cryptography, + * especially when the same key is reused. + * + * Example: + * Plaintext: "Hello!" + * Key: "key" + * Encrypted: "27090c03120b" + * Decrypted: "Hello!" * + * Reference: XOR Cipher - Wikipedia + * + * @author lcsjunior */ public final class XORCipher { + // Default character encoding for string conversion private static final Charset CS_DEFAULT = StandardCharsets.UTF_8; private XORCipher() { } + /** + * Applies the XOR operation between the input bytes and the key bytes. + * If the key is shorter than the input, it wraps around (cyclically). + * + * @param inputBytes The input byte array (plaintext or ciphertext). + * @param keyBytes The key byte array used for XOR operation. + * @return A new byte array containing the XOR result. + */ public static byte[] xor(final byte[] inputBytes, final byte[] keyBytes) { byte[] outputBytes = new byte[inputBytes.length]; for (int i = 0; i < inputBytes.length; ++i) { @@ -25,14 +53,40 @@ public static byte[] xor(final byte[] inputBytes, final byte[] keyBytes) { return outputBytes; } + /** + * Encrypts the given plaintext using the XOR cipher with the specified key. + * The result is a hexadecimal-encoded string representing the ciphertext. + * + * @param plainText The input plaintext to encrypt. + * @param key The encryption key. + * @throws IllegalArgumentException if the key is empty. + * @return A hexadecimal string representing the encrypted text. + */ public static String encrypt(final String plainText, final String key) { + if (key.isEmpty()) { + throw new IllegalArgumentException("Key must not be empty"); + } + byte[] plainTextBytes = plainText.getBytes(CS_DEFAULT); byte[] keyBytes = key.getBytes(CS_DEFAULT); byte[] xorResult = xor(plainTextBytes, keyBytes); return HexFormat.of().formatHex(xorResult); } + /** + * Decrypts the given ciphertext (in hexadecimal format) using the XOR cipher + * with the specified key. The result is the original plaintext. + * + * @param cipherText The hexadecimal string representing the encrypted text. + * @param key The decryption key (must be the same as the encryption key). + * @throws IllegalArgumentException if the key is empty. + * @return The decrypted plaintext. + */ public static String decrypt(final String cipherText, final String key) { + if (key.isEmpty()) { + throw new IllegalArgumentException("Key must not be empty"); + } + byte[] cipherBytes = HexFormat.of().parseHex(cipherText); byte[] keyBytes = key.getBytes(CS_DEFAULT); byte[] xorResult = xor(cipherBytes, keyBytes); diff --git a/src/main/java/com/thealgorithms/conversions/AffineConverter.java b/src/main/java/com/thealgorithms/conversions/AffineConverter.java index a580b23f90f9..199a6dd517d5 100644 --- a/src/main/java/com/thealgorithms/conversions/AffineConverter.java +++ b/src/main/java/com/thealgorithms/conversions/AffineConverter.java @@ -1,23 +1,64 @@ package com.thealgorithms.conversions; +/** + * A utility class to perform affine transformations of the form: + * y = slope * x + intercept. + * + * This class supports inversion and composition of affine transformations. + * It is immutable, meaning each instance represents a fixed transformation. + */ public final class AffineConverter { private final double slope; private final double intercept; + + /** + * Constructs an AffineConverter with the given slope and intercept. + * + * @param inSlope The slope of the affine transformation. + * @param inIntercept The intercept (constant term) of the affine transformation. + * @throws IllegalArgumentException if either parameter is NaN. + */ public AffineConverter(final double inSlope, final double inIntercept) { + if (Double.isNaN(inSlope) || Double.isNaN(inIntercept)) { + throw new IllegalArgumentException("Slope and intercept must be valid numbers."); + } slope = inSlope; intercept = inIntercept; } + /** + * Converts the given input value using the affine transformation: + * result = slope * inValue + intercept. + * + * @param inValue The input value to convert. + * @return The transformed value. + */ public double convert(final double inValue) { return slope * inValue + intercept; } + /** + * Returns a new AffineConverter representing the inverse of the current transformation. + * The inverse of y = slope * x + intercept is x = (y - intercept) / slope. + * + * @return A new AffineConverter representing the inverse transformation. + * @throws AssertionError if the slope is zero, as the inverse would be undefined. + */ public AffineConverter invert() { - assert slope != 0.0; + assert slope != 0.0 : "Slope cannot be zero for inversion."; return new AffineConverter(1.0 / slope, -intercept / slope); } + /** + * Composes this affine transformation with another, returning a new AffineConverter. + * If this transformation is f(x) and the other is g(x), the result is f(g(x)). + * + * @param other Another AffineConverter to compose with. + * @return A new AffineConverter representing the composition of the two transformations. + */ public AffineConverter compose(final AffineConverter other) { - return new AffineConverter(slope * other.slope, slope * other.intercept + intercept); + double newSlope = slope * other.slope; + double newIntercept = slope * other.intercept + intercept; + return new AffineConverter(newSlope, newIntercept); } } diff --git a/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java b/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java index 4bd9c74a1751..7a9448fd8fe7 100644 --- a/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java +++ b/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java @@ -136,7 +136,7 @@ public static String base2base(String n, int b1, int b2) { int decimalValue = 0; int charB2; char charB1; - String output = ""; + StringBuilder output = new StringBuilder(); // Go through every character of n for (int i = 0; i < n.length(); i++) { // store the character in charB1 @@ -167,15 +167,15 @@ public static String base2base(String n, int b1, int b2) { // If the remainder is a digit < 10, simply add it to // the left side of the new number. if (decimalValue % b2 < 10) { - output = decimalValue % b2 + output; + output.insert(0, decimalValue % b2); } // If the remainder is >= 10, add a character with the // corresponding value to the new number. (A = 10, B = 11, C = 12, ...) else { - output = (char) ((decimalValue % b2) + 55) + output; + output.insert(0, (char) ((decimalValue % b2) + 55)); } // Divide by the new base again decimalValue /= b2; } - return output; + return output.toString(); } } diff --git a/src/main/java/com/thealgorithms/conversions/AnytoAny.java b/src/main/java/com/thealgorithms/conversions/AnytoAny.java index 801e493032e0..e7bdbc2b79c4 100644 --- a/src/main/java/com/thealgorithms/conversions/AnytoAny.java +++ b/src/main/java/com/thealgorithms/conversions/AnytoAny.java @@ -1,35 +1,68 @@ package com.thealgorithms.conversions; -import java.util.Scanner; - -// given a source number , source base, destination base, this code can give you the destination -// number. -// sn ,sb,db ---> ()dn . this is what we have to do . - +/** + * A utility class for converting numbers from any base to any other base. + * + * This class provides a method to convert a source number from a given base + * to a destination number in another base. Valid bases range from 2 to 10. + */ public final class AnytoAny { private AnytoAny() { } - public static void main(String[] args) { - Scanner scn = new Scanner(System.in); - int sn = scn.nextInt(); - int sb = scn.nextInt(); - int db = scn.nextInt(); - int m = 1; - int dec = 0; - int dn = 0; - while (sn != 0) { - dec = dec + (sn % 10) * m; - m *= sb; - sn /= 10; + /** + * Converts a number from a source base to a destination base. + * + * @param sourceNumber The number in the source base (as an integer). + * @param sourceBase The base of the source number (between 2 and 10). + * @param destBase The base to which the number should be converted (between 2 and 10). + * @throws IllegalArgumentException if the bases are not between 2 and 10. + * @return The converted number in the destination base (as an integer). + */ + public static int convertBase(int sourceNumber, int sourceBase, int destBase) { + if (sourceBase < 2 || sourceBase > 10 || destBase < 2 || destBase > 10) { + throw new IllegalArgumentException("Bases must be between 2 and 10."); + } + + int decimalValue = toDecimal(sourceNumber, sourceBase); + return fromDecimal(decimalValue, destBase); + } + + /** + * Converts a number from a given base to its decimal representation (base 10). + * + * @param number The number in the original base. + * @param base The base of the given number. + * @return The decimal representation of the number. + */ + private static int toDecimal(int number, int base) { + int decimalValue = 0; + int multiplier = 1; + + while (number != 0) { + decimalValue += (number % 10) * multiplier; + multiplier *= base; + number /= 10; } - m = 1; - while (dec != 0) { - dn = dn + (dec % db) * m; - m *= 10; - dec /= db; + return decimalValue; + } + + /** + * Converts a decimal (base 10) number to a specified base. + * + * @param decimal The decimal number to convert. + * @param base The destination base for conversion. + * @return The number in the specified base. + */ + private static int fromDecimal(int decimal, int base) { + int result = 0; + int multiplier = 1; + + while (decimal != 0) { + result += (decimal % base) * multiplier; + multiplier *= 10; + decimal /= base; } - System.out.println(dn); - scn.close(); + return result; } } diff --git a/src/main/java/com/thealgorithms/conversions/EndianConverter.java b/src/main/java/com/thealgorithms/conversions/EndianConverter.java new file mode 100644 index 000000000000..0d69098e8255 --- /dev/null +++ b/src/main/java/com/thealgorithms/conversions/EndianConverter.java @@ -0,0 +1,47 @@ +package com.thealgorithms.conversions; + +/** + * Utility class for converting integers between big-endian and little-endian formats. + *

+ * Endianness defines how byte sequences represent multi-byte data types: + *

    + *
  • Big-endian: The most significant byte (MSB) comes first.
  • + *
  • Little-endian: The least significant byte (LSB) comes first.
  • + *
+ *

+ * Example conversion: + *

    + *
  • Big-endian to little-endian: {@code 0x12345678} → {@code 0x78563412}
  • + *
  • Little-endian to big-endian: {@code 0x78563412} → {@code 0x12345678}
  • + *
+ * + *

Note: Both conversions in this utility are equivalent since reversing the bytes is symmetric.

+ * + *

This class only supports 32-bit integers.

+ * + * @author Hardvan + */ +public final class EndianConverter { + private EndianConverter() { + } + + /** + * Converts a 32-bit integer from big-endian to little-endian. + * + * @param value the integer in big-endian format + * @return the integer in little-endian format + */ + public static int bigToLittleEndian(int value) { + return Integer.reverseBytes(value); + } + + /** + * Converts a 32-bit integer from little-endian to big-endian. + * + * @param value the integer in little-endian format + * @return the integer in big-endian format + */ + public static int littleToBigEndian(int value) { + return Integer.reverseBytes(value); + } +} diff --git a/src/main/java/com/thealgorithms/conversions/HexaDecimalToBinary.java b/src/main/java/com/thealgorithms/conversions/HexaDecimalToBinary.java index b6228488dc76..c0eb9a01ba17 100644 --- a/src/main/java/com/thealgorithms/conversions/HexaDecimalToBinary.java +++ b/src/main/java/com/thealgorithms/conversions/HexaDecimalToBinary.java @@ -1,43 +1,62 @@ package com.thealgorithms.conversions; -// Hex [0-9],[A-F] -> Binary [0,1] +/** + * Utility class for converting hexadecimal numbers to binary representation. + *

+ * A hexadecimal number consists of digits from {@code [0-9]} and {@code [A-F]} (case-insensitive), + * while binary representation uses only {@code [0, 1]}. + *

+ * This class provides methods to: + *

    + *
  • Convert a hexadecimal string to its binary string equivalent.
  • + *
  • Ensure the binary output is padded to 8 bits (1 byte).
  • + *
+ *

+ * Example: + *

    + *
  • {@code "A1"} → {@code "10100001"}
  • + *
  • {@code "1"} → {@code "00000001"}
  • + *
+ * + *

This class assumes that the input hexadecimal string is valid.

+ */ public class HexaDecimalToBinary { + + /** + * Converts a hexadecimal string to its binary string equivalent. + * The binary output is padded to a minimum of 8 bits (1 byte). + * Steps: + *
    + *
  1. Convert the hexadecimal string to an integer.
  2. + *
  3. Convert the integer to a binary string.
  4. + *
  5. Pad the binary string to ensure it is at least 8 bits long.
  6. + *
  7. Return the padded binary string.
  8. + *
+ * + * @param numHex the hexadecimal string (e.g., "A1", "7F") + * @throws NumberFormatException if the input string is not a valid hexadecimal number + * @return the binary string representation, padded to 8 bits (e.g., "10100001") + */ public String convert(String numHex) { - // String a HexaDecimal: int conHex = Integer.parseInt(numHex, 16); - // Hex a Binary: String binary = Integer.toBinaryString(conHex); - // Output: return completeDigits(binary); } + /** + * Pads the binary string to ensure it is at least 8 bits long. + * If the binary string is shorter than 8 bits, it adds leading zeros. + * + * @param binNum the binary string to pad + * @return the padded binary string with a minimum length of 8 + */ public String completeDigits(String binNum) { - final int longBits = 8; - for (int i = binNum.length(); i < longBits; i++) { - binNum = "0" + binNum; + final int byteSize = 8; + StringBuilder binNumBuilder = new StringBuilder(binNum); + while (binNumBuilder.length() < byteSize) { + binNumBuilder.insert(0, "0"); } + binNum = binNumBuilder.toString(); return binNum; } - - public static void main(String[] args) { - // Testing Numbers: - String[] hexNums = { - "1", - "A1", - "ef", - "BA", - "AA", - "BB", - "19", - "01", - "02", - "03", - "04", - }; - HexaDecimalToBinary objConvert = new HexaDecimalToBinary(); - - for (String num : hexNums) { - System.out.println(num + " = " + objConvert.convert(num)); - } - } } diff --git a/src/main/java/com/thealgorithms/conversions/HexaDecimalToDecimal.java b/src/main/java/com/thealgorithms/conversions/HexaDecimalToDecimal.java index 003781da9d5e..2cf6024d90a3 100644 --- a/src/main/java/com/thealgorithms/conversions/HexaDecimalToDecimal.java +++ b/src/main/java/com/thealgorithms/conversions/HexaDecimalToDecimal.java @@ -1,39 +1,45 @@ package com.thealgorithms.conversions; -import java.util.Scanner; - +/** + * Utility class for converting a hexadecimal string to its decimal representation. + *

+ * A hexadecimal number uses the base-16 numeral system, with the following characters: + *

    + *
  • Digits: 0-9
  • + *
  • Letters: A-F (case-insensitive)
  • + *
+ * Each character represents a power of 16. For example: + *
+ *   Hexadecimal "A1" = 10*16^1 + 1*16^0 = 161 (decimal)
+ * 
+ * + *

This class provides a method to perform the conversion without using built-in Java utilities.

+ */ public final class HexaDecimalToDecimal { private HexaDecimalToDecimal() { } - // convert hexadecimal to decimal + /** + * Converts a hexadecimal string to its decimal integer equivalent. + *

The input string is case-insensitive, and must contain valid hexadecimal characters [0-9, A-F].

+ * + * @param hex the hexadecimal string to convert + * @return the decimal integer representation of the input hexadecimal string + * @throws IllegalArgumentException if the input string contains invalid characters + */ public static int getHexaToDec(String hex) { String digits = "0123456789ABCDEF"; hex = hex.toUpperCase(); int val = 0; + for (int i = 0; i < hex.length(); i++) { int d = digits.indexOf(hex.charAt(i)); + if (d == -1) { + throw new IllegalArgumentException("Invalid hexadecimal character: " + hex.charAt(i)); + } val = 16 * val + d; } - return val; - } - // Main method gets the hexadecimal input from user and converts it into Decimal output. - public static void main(String[] args) { - String hexaInput; - int decOutput; - Scanner scan = new Scanner(System.in); - - System.out.print("Enter Hexadecimal Number : "); - hexaInput = scan.nextLine(); - - // convert hexadecimal to decimal - decOutput = getHexaToDec(hexaInput); - /* - Pass the string to the getHexaToDec function - and it returns the decimal form in the variable decOutput. - */ - System.out.println("Number in Decimal: " + decOutput); - scan.close(); + return val; } } diff --git a/src/main/java/com/thealgorithms/conversions/IPConverter.java b/src/main/java/com/thealgorithms/conversions/IPConverter.java new file mode 100644 index 000000000000..765cb0201dd5 --- /dev/null +++ b/src/main/java/com/thealgorithms/conversions/IPConverter.java @@ -0,0 +1,58 @@ +package com.thealgorithms.conversions; + +/** + * Converts an IPv4 address to its binary equivalent and vice-versa. + * IP to Binary: Converts an IPv4 address to its binary equivalent. + * Example: 127.3.4.5 -> 01111111.00000011.00000100.00000101 + * + * Binary to IP: Converts a binary equivalent to an IPv4 address. + * Example: 01111111.00000011.00000100.00000101 -> 127.3.4.5 + * + * @author Hardvan + */ +public final class IPConverter { + private IPConverter() { + } + + /** + * Converts an IPv4 address to its binary equivalent. + * @param ip The IPv4 address to convert. + * @return The binary equivalent of the IPv4 address. + */ + public static String ipToBinary(String ip) { + StringBuilder binary = new StringBuilder(); + for (String octet : ip.split("\\.")) { + binary.append(octetToBinary(Integer.parseInt(octet))).append("."); + } + return binary.substring(0, binary.length() - 1); + } + + /** + * Converts a single octet to its 8-bit binary representation. + * @param octet The octet to convert (0-255). + * @return The 8-bit binary representation as a String. + */ + private static String octetToBinary(int octet) { + char[] binary = {'0', '0', '0', '0', '0', '0', '0', '0'}; + for (int i = 7; i >= 0; i--) { + if ((octet & 1) == 1) { + binary[i] = '1'; + } + octet >>>= 1; + } + return new String(binary); + } + + /** + * Converts a binary equivalent to an IPv4 address. + * @param binary The binary equivalent to convert. + * @return The IPv4 address of the binary equivalent. + */ + public static String binaryToIP(String binary) { + StringBuilder ip = new StringBuilder(); + for (String octet : binary.split("\\.")) { + ip.append(Integer.parseInt(octet, 2)).append("."); + } + return ip.substring(0, ip.length() - 1); + } +} diff --git a/src/main/java/com/thealgorithms/conversions/IPv6Converter.java b/src/main/java/com/thealgorithms/conversions/IPv6Converter.java new file mode 100644 index 000000000000..d42ffd027514 --- /dev/null +++ b/src/main/java/com/thealgorithms/conversions/IPv6Converter.java @@ -0,0 +1,98 @@ +package com.thealgorithms.conversions; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; + +/** + * A utility class for converting between IPv6 and IPv4 addresses. + * + * - Converts IPv4 to IPv6-mapped IPv6 address. + * - Extracts IPv4 address from IPv6-mapped IPv6. + * - Handles exceptions for invalid inputs. + * + * @author Hardvan + */ +public final class IPv6Converter { + private IPv6Converter() { + } + + /** + * Converts an IPv4 address (e.g., "192.0.2.128") to an IPv6-mapped IPv6 address. + * Example: IPv4 "192.0.2.128" -> IPv6 "::ffff:192.0.2.128" + * + * @param ipv4Address The IPv4 address in string format. + * @return The corresponding IPv6-mapped IPv6 address. + * @throws UnknownHostException If the IPv4 address is invalid. + * @throws IllegalArgumentException If the IPv6 address is not a mapped IPv4 address. + */ + public static String ipv4ToIpv6(String ipv4Address) throws UnknownHostException { + if (ipv4Address == null || ipv4Address.isEmpty()) { + throw new UnknownHostException("IPv4 address is empty."); + } + + InetAddress ipv4 = InetAddress.getByName(ipv4Address); + byte[] ipv4Bytes = ipv4.getAddress(); + + // Create IPv6-mapped IPv6 address (starts with ::ffff:) + byte[] ipv6Bytes = new byte[16]; + ipv6Bytes[10] = (byte) 0xff; + ipv6Bytes[11] = (byte) 0xff; + System.arraycopy(ipv4Bytes, 0, ipv6Bytes, 12, 4); + + // Manually format to "::ffff:x.x.x.x" format + StringBuilder ipv6String = new StringBuilder("::ffff:"); + for (int i = 12; i < 16; i++) { + ipv6String.append(ipv6Bytes[i] & 0xFF); + if (i < 15) { + ipv6String.append('.'); + } + } + return ipv6String.toString(); + } + + /** + * Extracts the IPv4 address from an IPv6-mapped IPv6 address. + * Example: IPv6 "::ffff:192.0.2.128" -> IPv4 "192.0.2.128" + * + * @param ipv6Address The IPv6 address in string format. + * @return The extracted IPv4 address. + * @throws UnknownHostException If the IPv6 address is invalid or not a mapped IPv4 address. + */ + public static String ipv6ToIpv4(String ipv6Address) throws UnknownHostException { + InetAddress ipv6 = InetAddress.getByName(ipv6Address); + byte[] ipv6Bytes = ipv6.getAddress(); + + // Check if the address is an IPv6-mapped IPv4 address + if (isValidIpv6MappedIpv4(ipv6Bytes)) { + byte[] ipv4Bytes = Arrays.copyOfRange(ipv6Bytes, 12, 16); + InetAddress ipv4 = InetAddress.getByAddress(ipv4Bytes); + return ipv4.getHostAddress(); + } else { + throw new IllegalArgumentException("Not a valid IPv6-mapped IPv4 address."); + } + } + + /** + * Helper function to check if the given byte array represents + * an IPv6-mapped IPv4 address (prefix 0:0:0:0:0:ffff). + * + * @param ipv6Bytes Byte array representation of the IPv6 address. + * @return True if the address is IPv6-mapped IPv4, otherwise false. + */ + private static boolean isValidIpv6MappedIpv4(byte[] ipv6Bytes) { + // IPv6-mapped IPv4 addresses are 16 bytes long, with the first 10 bytes set to 0, + // followed by 0xff, 0xff, and the last 4 bytes representing the IPv4 address. + if (ipv6Bytes.length != 16) { + return false; + } + + for (int i = 0; i < 10; i++) { + if (ipv6Bytes[i] != 0) { + return false; + } + } + + return ipv6Bytes[10] == (byte) 0xff && ipv6Bytes[11] == (byte) 0xff; + } +} diff --git a/src/main/java/com/thealgorithms/conversions/IntegerToEnglish.java b/src/main/java/com/thealgorithms/conversions/IntegerToEnglish.java index d3b938bf492d..e85c608af5d0 100644 --- a/src/main/java/com/thealgorithms/conversions/IntegerToEnglish.java +++ b/src/main/java/com/thealgorithms/conversions/IntegerToEnglish.java @@ -2,7 +2,28 @@ import java.util.Map; +/** + * A utility class to convert integers to their English word representation. + * + *

The class supports conversion of numbers from 0 to 2,147,483,647 + * (the maximum value of a 32-bit signed integer). It divides the number + * into groups of three digits (thousands, millions, billions, etc.) and + * translates each group into words.

+ * + *

Example Usage

+ *
+ *   IntegerToEnglish.integerToEnglishWords(12345);
+ *   // Output: "Twelve Thousand Three Hundred Forty Five"
+ * 
+ * + *

This class uses two maps:

+ *
    + *
  • BASE_NUMBERS_MAP: Holds English words for numbers 0-20, multiples of 10 up to 90, and 100.
  • + *
  • THOUSAND_POWER_MAP: Maps powers of 1000 (e.g., Thousand, Million, Billion).
  • + *
+ */ public final class IntegerToEnglish { + private static final Map BASE_NUMBERS_MAP = Map.ofEntries(Map.entry(0, ""), Map.entry(1, "One"), Map.entry(2, "Two"), Map.entry(3, "Three"), Map.entry(4, "Four"), Map.entry(5, "Five"), Map.entry(6, "Six"), Map.entry(7, "Seven"), Map.entry(8, "Eight"), Map.entry(9, "Nine"), Map.entry(10, "Ten"), Map.entry(11, "Eleven"), Map.entry(12, "Twelve"), Map.entry(13, "Thirteen"), Map.entry(14, "Fourteen"), Map.entry(15, "Fifteen"), Map.entry(16, "Sixteen"), Map.entry(17, "Seventeen"), Map.entry(18, "Eighteen"), Map.entry(19, "Nineteen"), Map.entry(20, "Twenty"), Map.entry(30, "Thirty"), Map.entry(40, "Forty"), Map.entry(50, "Fifty"), Map.entry(60, "Sixty"), Map.entry(70, "Seventy"), Map.entry(80, "Eighty"), Map.entry(90, "Ninety"), Map.entry(100, "Hundred")); @@ -13,35 +34,46 @@ private IntegerToEnglish() { } /** - converts numbers < 1000 to english words + * Converts numbers less than 1000 into English words. + * + * @param number the integer value (0-999) to convert + * @return the English word representation of the input number */ private static String convertToWords(int number) { int remainder = number % 100; - - String result; + StringBuilder result = new StringBuilder(); if (remainder <= 20) { - result = BASE_NUMBERS_MAP.get(remainder); + result.append(BASE_NUMBERS_MAP.get(remainder)); } else if (BASE_NUMBERS_MAP.containsKey(remainder)) { - result = BASE_NUMBERS_MAP.get(remainder); + result.append(BASE_NUMBERS_MAP.get(remainder)); } else { int tensDigit = remainder / 10; int onesDigit = remainder % 10; - - result = String.format("%s %s", BASE_NUMBERS_MAP.get(tensDigit * 10), BASE_NUMBERS_MAP.get(onesDigit)); + String tens = BASE_NUMBERS_MAP.getOrDefault(tensDigit * 10, ""); + String ones = BASE_NUMBERS_MAP.getOrDefault(onesDigit, ""); + result.append(tens); + if (ones != null && !ones.isEmpty()) { + result.append(" ").append(ones); + } } int hundredsDigit = number / 100; - if (hundredsDigit > 0) { - result = String.format("%s %s%s", BASE_NUMBERS_MAP.get(hundredsDigit), BASE_NUMBERS_MAP.get(100), result.isEmpty() ? "" : " " + result); + if (result.length() > 0) { + result.insert(0, " "); + } + result.insert(0, String.format("%s Hundred", BASE_NUMBERS_MAP.get(hundredsDigit))); } - return result; + return result.toString().trim(); } /** - Only convert groups of three digit if they are non-zero + * Converts a non-negative integer to its English word representation. + * + * @param number the integer to convert (0-2,147,483,647) + * @return the English word representation of the input number */ public static String integerToEnglishWords(int number) { if (number == 0) { @@ -49,7 +81,6 @@ public static String integerToEnglishWords(int number) { } StringBuilder result = new StringBuilder(); - int index = 0; while (number > 0) { @@ -58,23 +89,20 @@ public static String integerToEnglishWords(int number) { if (remainder > 0) { String subResult = convertToWords(remainder); - if (!subResult.isEmpty()) { - if (!result.isEmpty()) { - result.insert(0, subResult + " " + THOUSAND_POWER_MAP.get(index) + " "); - } else { - if (index > 0) { - result = new StringBuilder(subResult + " " + THOUSAND_POWER_MAP.get(index)); - } else { - result = new StringBuilder(subResult); - } + if (index > 0) { + subResult += " " + THOUSAND_POWER_MAP.get(index); + } + if (result.length() > 0) { + result.insert(0, " "); } + result.insert(0, subResult); } } index++; } - return result.toString(); + return result.toString().trim(); } } diff --git a/src/main/java/com/thealgorithms/conversions/IntegerToRoman.java b/src/main/java/com/thealgorithms/conversions/IntegerToRoman.java index 9c031df9504d..fec437668fe6 100644 --- a/src/main/java/com/thealgorithms/conversions/IntegerToRoman.java +++ b/src/main/java/com/thealgorithms/conversions/IntegerToRoman.java @@ -1,68 +1,68 @@ package com.thealgorithms.conversions; /** - * Converting Integers into Roman Numerals + * A utility class to convert integers into Roman numerals. * - *

- * ('I', 1); ('IV',4); ('V', 5); ('IX',9); ('X', 10); ('XL',40); ('L', 50); - * ('XC',90); ('C', 100); ('D', 500); ('M', 1000); + *

Roman numerals follow these rules: + *

    + *
  • I = 1
  • + *
  • IV = 4
  • + *
  • V = 5
  • + *
  • IX = 9
  • + *
  • X = 10
  • + *
  • XL = 40
  • + *
  • L = 50
  • + *
  • XC = 90
  • + *
  • C = 100
  • + *
  • D = 500
  • + *
  • M = 1000
  • + *
+ * + *

Conversion is based on repeatedly subtracting the largest possible Roman numeral value + * from the input number until it reaches zero. For example, 1994 is converted as: + *

+ *   1994 -> MCMXCIV (1000 + 900 + 90 + 4)
+ * 
*/ public final class IntegerToRoman { + + // Array of Roman numeral values in descending order + private static final int[] ALL_ROMAN_NUMBERS_IN_ARABIC = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}; + + // Corresponding Roman numeral symbols + private static final String[] ALL_ROMAN_NUMBERS = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"}; + private IntegerToRoman() { } - private static final int[] ALL_ROMAN_NUMBERS_IN_ARABIC = new int[] { - 1000, - 900, - 500, - 400, - 100, - 90, - 50, - 40, - 10, - 9, - 5, - 4, - 1, - }; - private static final String[] ALL_ROMAN_NUMBERS = new String[] { - "M", - "CM", - "D", - "CD", - "C", - "XC", - "L", - "XL", - "X", - "IX", - "V", - "IV", - "I", - }; - - // Value must be > 0 + /** + * Converts an integer to its Roman numeral representation. + * Steps: + *
    + *
  1. Iterate over the Roman numeral values in descending order
  2. + *
  3. Calculate how many times a numeral fits
  4. + *
  5. Append the corresponding symbol
  6. + *
  7. Subtract the value from the number
  8. + *
  9. Repeat until the number is zero
  10. + *
  11. Return the Roman numeral representation
  12. + *
+ * + * @param num the integer value to convert (must be greater than 0) + * @return the Roman numeral representation of the input integer + * or an empty string if the input is non-positive + */ public static String integerToRoman(int num) { if (num <= 0) { return ""; } StringBuilder builder = new StringBuilder(); - - for (int a = 0; a < ALL_ROMAN_NUMBERS_IN_ARABIC.length; a++) { - int times = num / ALL_ROMAN_NUMBERS_IN_ARABIC[a]; - for (int b = 0; b < times; b++) { - builder.append(ALL_ROMAN_NUMBERS[a]); - } - - num -= times * ALL_ROMAN_NUMBERS_IN_ARABIC[a]; + for (int i = 0; i < ALL_ROMAN_NUMBERS_IN_ARABIC.length; i++) { + int times = num / ALL_ROMAN_NUMBERS_IN_ARABIC[i]; + builder.append(ALL_ROMAN_NUMBERS[i].repeat(Math.max(0, times))); + num -= times * ALL_ROMAN_NUMBERS_IN_ARABIC[i]; } return builder.toString(); } - - public static void main(String[] args) { - System.out.println(IntegerToRoman.integerToRoman(2131)); - } } diff --git a/src/main/java/com/thealgorithms/conversions/MorseCodeConverter.java b/src/main/java/com/thealgorithms/conversions/MorseCodeConverter.java new file mode 100644 index 000000000000..a3973da0c586 --- /dev/null +++ b/src/main/java/com/thealgorithms/conversions/MorseCodeConverter.java @@ -0,0 +1,98 @@ +package com.thealgorithms.conversions; + +import java.util.HashMap; +import java.util.Map; + +/** + * Converts text to Morse code and vice-versa. + * Text to Morse code: Each letter is separated by a space and each word is separated by a pipe (|). + * Example: "HELLO WORLD" -> ".... . .-.. .-.. --- | .-- --- .-. .-.. -.." + * + * Morse code to text: Each letter is separated by a space and each word is separated by a pipe (|). + * Example: ".... . .-.. .-.. --- | .-- --- .-. .-.. -.." -> "HELLO WORLD" + * + * Applications: Used in radio communications and algorithmic challenges. + * + * @author Hardvan + */ +public final class MorseCodeConverter { + private MorseCodeConverter() { + } + + private static final Map MORSE_MAP = new HashMap<>(); + private static final Map REVERSE_MAP = new HashMap<>(); + + static { + MORSE_MAP.put('A', ".-"); + MORSE_MAP.put('B', "-..."); + MORSE_MAP.put('C', "-.-."); + MORSE_MAP.put('D', "-.."); + MORSE_MAP.put('E', "."); + MORSE_MAP.put('F', "..-."); + MORSE_MAP.put('G', "--."); + MORSE_MAP.put('H', "...."); + MORSE_MAP.put('I', ".."); + MORSE_MAP.put('J', ".---"); + MORSE_MAP.put('K', "-.-"); + MORSE_MAP.put('L', ".-.."); + MORSE_MAP.put('M', "--"); + MORSE_MAP.put('N', "-."); + MORSE_MAP.put('O', "---"); + MORSE_MAP.put('P', ".--."); + MORSE_MAP.put('Q', "--.-"); + MORSE_MAP.put('R', ".-."); + MORSE_MAP.put('S', "..."); + MORSE_MAP.put('T', "-"); + MORSE_MAP.put('U', "..-"); + MORSE_MAP.put('V', "...-"); + MORSE_MAP.put('W', ".--"); + MORSE_MAP.put('X', "-..-"); + MORSE_MAP.put('Y', "-.--"); + MORSE_MAP.put('Z', "--.."); + + // Build reverse map for decoding + MORSE_MAP.forEach((k, v) -> REVERSE_MAP.put(v, k)); + } + + /** + * Converts text to Morse code. + * Each letter is separated by a space and each word is separated by a pipe (|). + * + * @param text The text to convert to Morse code. + * @return The Morse code representation of the text. + */ + public static String textToMorse(String text) { + StringBuilder morse = new StringBuilder(); + String[] words = text.toUpperCase().split(" "); + for (int i = 0; i < words.length; i++) { + for (char c : words[i].toCharArray()) { + morse.append(MORSE_MAP.getOrDefault(c, "")).append(" "); + } + if (i < words.length - 1) { + morse.append("| "); + } + } + return morse.toString().trim(); + } + + /** + * Converts Morse code to text. + * Each letter is separated by a space and each word is separated by a pipe (|). + * + * @param morse The Morse code to convert to text. + * @return The text representation of the Morse code. + */ + public static String morseToText(String morse) { + StringBuilder text = new StringBuilder(); + String[] words = morse.split(" \\| "); + for (int i = 0; i < words.length; i++) { + for (String code : words[i].split(" ")) { + text.append(REVERSE_MAP.getOrDefault(code, '?')); + } + if (i < words.length - 1) { + text.append(" "); + } + } + return text.toString(); + } +} diff --git a/src/main/java/com/thealgorithms/conversions/NumberToWords.java b/src/main/java/com/thealgorithms/conversions/NumberToWords.java new file mode 100644 index 000000000000..e39c5b2dea86 --- /dev/null +++ b/src/main/java/com/thealgorithms/conversions/NumberToWords.java @@ -0,0 +1,100 @@ +package com.thealgorithms.conversions; + +import java.math.BigDecimal; + +/** + A Java-based utility for converting numeric values into their English word + representations. Whether you need to convert a small number, a large number + with millions and billions, or even a number with decimal places, this utility + has you covered. + * + */ +public final class NumberToWords { + + private NumberToWords() { + } + + private static final String[] UNITS = {"", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"}; + + private static final String[] TENS = {"", "", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"}; + + private static final String[] POWERS = {"", "Thousand", "Million", "Billion", "Trillion"}; + + private static final String ZERO = "Zero"; + private static final String POINT = " Point"; + private static final String NEGATIVE = "Negative "; + + public static String convert(BigDecimal number) { + if (number == null) { + return "Invalid Input"; + } + + // Check for negative sign + boolean isNegative = number.signum() < 0; + + // Split the number into whole and fractional parts + BigDecimal[] parts = number.abs().divideAndRemainder(BigDecimal.ONE); + BigDecimal wholePart = parts[0]; // Keep whole part as BigDecimal + String fractionalPartStr = parts[1].compareTo(BigDecimal.ZERO) > 0 ? parts[1].toPlainString().substring(2) : ""; // Get fractional part only if it exists + + // Convert whole part to words + StringBuilder result = new StringBuilder(); + if (isNegative) { + result.append(NEGATIVE); + } + result.append(convertWholeNumberToWords(wholePart)); + + // Convert fractional part to words + if (!fractionalPartStr.isEmpty()) { + result.append(POINT); + for (char digit : fractionalPartStr.toCharArray()) { + int digitValue = Character.getNumericValue(digit); + result.append(" ").append(digitValue == 0 ? ZERO : UNITS[digitValue]); + } + } + + return result.toString().trim(); + } + + private static String convertWholeNumberToWords(BigDecimal number) { + if (number.compareTo(BigDecimal.ZERO) == 0) { + return ZERO; + } + + StringBuilder words = new StringBuilder(); + int power = 0; + + while (number.compareTo(BigDecimal.ZERO) > 0) { + // Get the last three digits + BigDecimal[] divisionResult = number.divideAndRemainder(BigDecimal.valueOf(1000)); + int chunk = divisionResult[1].intValue(); + + if (chunk > 0) { + String chunkWords = convertChunk(chunk); + if (power > 0) { + words.insert(0, POWERS[power] + " "); + } + words.insert(0, chunkWords + " "); + } + + number = divisionResult[0]; // Continue with the remaining part + power++; + } + + return words.toString().trim(); + } + + private static String convertChunk(int number) { + String chunkWords; + + if (number < 20) { + chunkWords = UNITS[number]; + } else if (number < 100) { + chunkWords = TENS[number / 10] + (number % 10 > 0 ? " " + UNITS[number % 10] : ""); + } else { + chunkWords = UNITS[number / 100] + " Hundred" + (number % 100 > 0 ? " " + convertChunk(number % 100) : ""); + } + + return chunkWords; + } +} diff --git a/src/main/java/com/thealgorithms/conversions/OctalToBinary.java b/src/main/java/com/thealgorithms/conversions/OctalToBinary.java index 6b01c2f65cfe..a66db97633b4 100644 --- a/src/main/java/com/thealgorithms/conversions/OctalToBinary.java +++ b/src/main/java/com/thealgorithms/conversions/OctalToBinary.java @@ -1,14 +1,40 @@ package com.thealgorithms.conversions; /** - * Converts any Octal Number to a Binary Number + * A utility class to convert an octal (base-8) number into its binary (base-2) representation. + * + *

This class provides methods to: + *

    + *
  • Convert an octal number to its binary equivalent
  • + *
  • Convert individual octal digits to binary
  • + *
+ * + *

Octal to Binary Conversion:

+ *

An octal number is converted to binary by converting each octal digit to its 3-bit binary equivalent. + * The result is a long representing the full binary equivalent of the octal number.

+ * + *

Example Usage

+ *
+ *   long binary = OctalToBinary.convertOctalToBinary(52); // Output: 101010 (52 in octal is 101010 in binary)
+ * 
* * @author Bama Charan Chhandogi + * @see Octal Number System + * @see Binary Number System */ - public final class OctalToBinary { private OctalToBinary() { } + + /** + * Converts an octal number to its binary representation. + * + *

Each octal digit is individually converted to its 3-bit binary equivalent, and the binary + * digits are concatenated to form the final binary number.

+ * + * @param octalNumber the octal number to convert (non-negative integer) + * @return the binary equivalent as a long + */ public static long convertOctalToBinary(int octalNumber) { long binaryNumber = 0; int digitPosition = 1; @@ -20,12 +46,25 @@ public static long convertOctalToBinary(int octalNumber) { binaryNumber += binaryDigit * digitPosition; octalNumber /= 10; - digitPosition *= 1000; // Move to the next group of 3 binary digits + digitPosition *= 1000; } return binaryNumber; } + /** + * Converts a single octal digit (0-7) to its binary equivalent. + * + *

For example: + *

    + *
  • Octal digit 7 is converted to binary 111
  • + *
  • Octal digit 3 is converted to binary 011
  • + *
+ *

+ * + * @param octalDigit a single octal digit (0-7) + * @return the binary equivalent as a long + */ public static long convertOctalDigitToBinary(int octalDigit) { long binaryDigit = 0; int binaryMultiplier = 1; diff --git a/src/main/java/com/thealgorithms/conversions/PhoneticAlphabetConverter.java b/src/main/java/com/thealgorithms/conversions/PhoneticAlphabetConverter.java new file mode 100644 index 000000000000..730ce2214e2d --- /dev/null +++ b/src/main/java/com/thealgorithms/conversions/PhoneticAlphabetConverter.java @@ -0,0 +1,84 @@ +package com.thealgorithms.conversions; + +import java.util.HashMap; +import java.util.Map; + +/** + * Converts text to the NATO phonetic alphabet. + * Examples: + * "ABC" -> "Alpha Bravo Charlie" + * "Hello" -> "Hotel Echo Lima Lima Oscar" + * "123" -> "One Two Three" + * "A1B2C3" -> "Alpha One Bravo Two Charlie Three" + * + * @author Hardvan + */ +public final class PhoneticAlphabetConverter { + private PhoneticAlphabetConverter() { + } + + private static final Map PHONETIC_MAP = new HashMap<>(); + + static { + PHONETIC_MAP.put('A', "Alpha"); + PHONETIC_MAP.put('B', "Bravo"); + PHONETIC_MAP.put('C', "Charlie"); + PHONETIC_MAP.put('D', "Delta"); + PHONETIC_MAP.put('E', "Echo"); + PHONETIC_MAP.put('F', "Foxtrot"); + PHONETIC_MAP.put('G', "Golf"); + PHONETIC_MAP.put('H', "Hotel"); + PHONETIC_MAP.put('I', "India"); + PHONETIC_MAP.put('J', "Juliett"); + PHONETIC_MAP.put('K', "Kilo"); + PHONETIC_MAP.put('L', "Lima"); + PHONETIC_MAP.put('M', "Mike"); + PHONETIC_MAP.put('N', "November"); + PHONETIC_MAP.put('O', "Oscar"); + PHONETIC_MAP.put('P', "Papa"); + PHONETIC_MAP.put('Q', "Quebec"); + PHONETIC_MAP.put('R', "Romeo"); + PHONETIC_MAP.put('S', "Sierra"); + PHONETIC_MAP.put('T', "Tango"); + PHONETIC_MAP.put('U', "Uniform"); + PHONETIC_MAP.put('V', "Victor"); + PHONETIC_MAP.put('W', "Whiskey"); + PHONETIC_MAP.put('X', "X-ray"); + PHONETIC_MAP.put('Y', "Yankee"); + PHONETIC_MAP.put('Z', "Zulu"); + PHONETIC_MAP.put('0', "Zero"); + PHONETIC_MAP.put('1', "One"); + PHONETIC_MAP.put('2', "Two"); + PHONETIC_MAP.put('3', "Three"); + PHONETIC_MAP.put('4', "Four"); + PHONETIC_MAP.put('5', "Five"); + PHONETIC_MAP.put('6', "Six"); + PHONETIC_MAP.put('7', "Seven"); + PHONETIC_MAP.put('8', "Eight"); + PHONETIC_MAP.put('9', "Nine"); + } + + /** + * Converts text to the NATO phonetic alphabet. + * Steps: + * 1. Convert the text to uppercase. + * 2. Iterate over each character in the text. + * 3. Get the phonetic equivalent of the character from the map. + * 4. Append the phonetic equivalent to the result. + * 5. Append a space to separate the phonetic equivalents. + * 6. Return the result. + * + * @param text the text to convert + * @return the NATO phonetic alphabet + */ + public static String textToPhonetic(String text) { + StringBuilder phonetic = new StringBuilder(); + for (char c : text.toUpperCase().toCharArray()) { + if (Character.isWhitespace(c)) { + continue; + } + phonetic.append(PHONETIC_MAP.getOrDefault(c, String.valueOf(c))).append(" "); + } + return phonetic.toString().trim(); + } +} diff --git a/src/main/java/com/thealgorithms/conversions/RomanToInteger.java b/src/main/java/com/thealgorithms/conversions/RomanToInteger.java index 1934e9b264c9..a634c720326f 100644 --- a/src/main/java/com/thealgorithms/conversions/RomanToInteger.java +++ b/src/main/java/com/thealgorithms/conversions/RomanToInteger.java @@ -3,9 +3,27 @@ import java.util.HashMap; import java.util.Map; +/** + * A utility class to convert Roman numerals into integers. + * + *

Roman numerals are based on seven symbols given below: + *

    + *
  • I = 1
  • + *
  • V = 5
  • + *
  • X = 10
  • + *
  • L = 50
  • + *
  • C = 100
  • + *
  • D = 500
  • + *
  • M = 1000
  • + *
+ * + *

If a smaller numeral appears before a larger numeral, it is subtracted. + * Otherwise, it is added. For example: + *

+ *   MCMXCIV = 1000 + (1000 - 100) + (100 - 10) + (5 - 1) = 1994
+ * 
+ */ public final class RomanToInteger { - private RomanToInteger() { - } private static final Map ROMAN_TO_INT = new HashMap<>() { { @@ -19,44 +37,53 @@ private RomanToInteger() { } }; + private RomanToInteger() { + } + + /** + * Converts a single Roman numeral character to its integer value. + * + * @param symbol the Roman numeral character + * @return the corresponding integer value + * @throws IllegalArgumentException if the symbol is not a valid Roman numeral + */ private static int romanSymbolToInt(final char symbol) { return ROMAN_TO_INT.computeIfAbsent(symbol, c -> { throw new IllegalArgumentException("Unknown Roman symbol: " + c); }); } - // Roman Number = Roman Numerals - /** - * This function convert Roman number into Integer + * Converts a Roman numeral string to its integer equivalent. + * Steps: + *
    + *
  1. Iterate over the string from right to left.
  2. + *
  3. For each character, convert it to an integer value.
  4. + *
  5. If the current value is greater than or equal to the max previous value, add it.
  6. + *
  7. Otherwise, subtract it from the sum.
  8. + *
  9. Update the max previous value.
  10. + *
  11. Return the sum.
  12. + *
* - * @param a Roman number string - * @return integer + * @param roman the Roman numeral string + * @return the integer value of the Roman numeral + * @throws IllegalArgumentException if the input contains invalid Roman characters + * @throws NullPointerException if the input is {@code null} */ - public static int romanToInt(String a) { - a = a.toUpperCase(); - char prev = ' '; + public static int romanToInt(String roman) { + if (roman == null) { + throw new NullPointerException("Input cannot be null"); + } + roman = roman.toUpperCase(); int sum = 0; - - int newPrev = 0; - for (int i = a.length() - 1; i >= 0; i--) { - char c = a.charAt(i); - - if (prev != ' ') { - // checking current Number greater than previous or not - newPrev = romanSymbolToInt(prev) > newPrev ? romanSymbolToInt(prev) : newPrev; - } - - int currentNum = romanSymbolToInt(c); - - // if current number greater than prev max previous then add - if (currentNum >= newPrev) { - sum += currentNum; + int maxPrevValue = 0; + for (int i = roman.length() - 1; i >= 0; i--) { + int currentValue = romanSymbolToInt(roman.charAt(i)); + if (currentValue >= maxPrevValue) { + sum += currentValue; + maxPrevValue = currentValue; } else { - // subtract upcoming number until upcoming number not greater than prev max - sum -= currentNum; + sum -= currentValue; } - - prev = c; } return sum; diff --git a/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java b/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java index 4d13b8b7fd55..30030de6c1bd 100644 --- a/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java +++ b/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java @@ -1,7 +1,5 @@ package com.thealgorithms.conversions; -import java.util.Scanner; - /** * Converts turkish character to latin character * @@ -11,21 +9,12 @@ public final class TurkishToLatinConversion { private TurkishToLatinConversion() { } - /** - * Main method - * - * @param args Command line arguments - */ - public static void main(String[] args) { - Scanner sc = new Scanner(System.in); - System.out.println("Input the string: "); - String b = sc.next(); - System.out.println("Converted: " + convertTurkishToLatin(b)); - sc.close(); - } - /** * This method converts a turkish character to latin character. + * Steps: + * 1. Define turkish characters and their corresponding latin characters + * 2. Replace all turkish characters with their corresponding latin characters + * 3. Return the converted string * * @param param String paramter * @return String diff --git a/src/main/java/com/thealgorithms/conversions/UnitConversions.java b/src/main/java/com/thealgorithms/conversions/UnitConversions.java index abc06a0f8863..15f74a21a17e 100644 --- a/src/main/java/com/thealgorithms/conversions/UnitConversions.java +++ b/src/main/java/com/thealgorithms/conversions/UnitConversions.java @@ -5,10 +5,47 @@ import java.util.Map; import org.apache.commons.lang3.tuple.Pair; +/** + * A utility class to perform unit conversions between different measurement systems. + * + *

Currently, the class supports temperature conversions between several scales: + * Celsius, Fahrenheit, Kelvin, Réaumur, Delisle, and Rankine. + * + *

Example Usage

+ *
+ *   double result = UnitConversions.TEMPERATURE.convert("Celsius", "Fahrenheit", 100.0);
+ *   // Output: 212.0 (Celsius to Fahrenheit conversion of 100°C)
+ * 
+ * + *

This class makes use of an {@link UnitsConverter} that handles the conversion logic + * based on predefined affine transformations. These transformations include scaling factors + * and offsets for temperature conversions. + * + *

Temperature Scales Supported

+ *
    + *
  • Celsius
  • + *
  • Fahrenheit
  • + *
  • Kelvin
  • + *
  • Réaumur
  • + *
  • Delisle
  • + *
  • Rankine
  • + *
+ */ public final class UnitConversions { private UnitConversions() { } + /** + * A preconfigured instance of {@link UnitsConverter} for temperature conversions. + * The converter handles conversions between the following temperature units: + *
    + *
  • Kelvin to Celsius
  • + *
  • Celsius to Fahrenheit
  • + *
  • Réaumur to Celsius
  • + *
  • Delisle to Celsius
  • + *
  • Rankine to Kelvin
  • + *
+ */ public static final UnitsConverter TEMPERATURE = new UnitsConverter(Map.ofEntries(entry(Pair.of("Kelvin", "Celsius"), new AffineConverter(1.0, -273.15)), entry(Pair.of("Celsius", "Fahrenheit"), new AffineConverter(9.0 / 5.0, 32.0)), entry(Pair.of("Réaumur", "Celsius"), new AffineConverter(5.0 / 4.0, 0.0)), entry(Pair.of("Delisle", "Celsius"), new AffineConverter(-2.0 / 3.0, 100.0)), entry(Pair.of("Rankine", "Kelvin"), new AffineConverter(5.0 / 9.0, 0.0)))); } diff --git a/src/main/java/com/thealgorithms/conversions/UnitsConverter.java b/src/main/java/com/thealgorithms/conversions/UnitsConverter.java index 81c4d4562070..00690b2c0f9b 100644 --- a/src/main/java/com/thealgorithms/conversions/UnitsConverter.java +++ b/src/main/java/com/thealgorithms/conversions/UnitsConverter.java @@ -7,6 +7,43 @@ import java.util.Set; import org.apache.commons.lang3.tuple.Pair; +/** + * A class that handles unit conversions using affine transformations. + * + *

The {@code UnitsConverter} allows converting values between different units using + * pre-defined affine conversion formulas. Each conversion is represented by an + * {@link AffineConverter} that defines the scaling and offset for the conversion. + * + *

For each unit, both direct conversions (e.g., Celsius to Fahrenheit) and inverse + * conversions (e.g., Fahrenheit to Celsius) are generated automatically. It also computes + * transitive conversions (e.g., Celsius to Kelvin via Fahrenheit if both conversions exist). + * + *

Key features include: + *

    + *
  • Automatic handling of inverse conversions (e.g., Fahrenheit to Celsius).
  • + *
  • Compositional conversions, meaning if conversions between A -> B and B -> C exist, + * it can automatically generate A -> C conversion.
  • + *
  • Supports multiple unit systems as long as conversions are provided in pairs.
  • + *
+ * + *

Example Usage

+ *
+ * Map<Pair<String, String>, AffineConverter> basicConversions = Map.ofEntries(
+ *     entry(Pair.of("Celsius", "Fahrenheit"), new AffineConverter(9.0 / 5.0, 32.0)),
+ *     entry(Pair.of("Kelvin", "Celsius"), new AffineConverter(1.0, -273.15))
+ * );
+ *
+ * UnitsConverter converter = new UnitsConverter(basicConversions);
+ * double result = converter.convert("Celsius", "Fahrenheit", 100.0);
+ * // Output: 212.0 (Celsius to Fahrenheit conversion of 100°C)
+ * 
+ * + *

Exception Handling

+ *
    + *
  • If the input unit and output unit are the same, an {@link IllegalArgumentException} is thrown.
  • + *
  • If a conversion between the requested units does not exist, a {@link NoSuchElementException} is thrown.
  • + *
+ */ public final class UnitsConverter { private final Map, AffineConverter> conversions; private final Set units; @@ -68,11 +105,29 @@ private static Set extractUnits(final Map, AffineCo return res; } + /** + * Constructor for {@code UnitsConverter}. + * + *

Accepts a map of basic conversions and automatically generates inverse and + * transitive conversions. + * + * @param basicConversions the initial set of unit conversions to add. + */ public UnitsConverter(final Map, AffineConverter> basicConversions) { conversions = computeAllConversions(basicConversions); units = extractUnits(conversions); } + /** + * Converts a value from one unit to another. + * + * @param inputUnit the unit of the input value. + * @param outputUnit the unit to convert the value into. + * @param value the value to convert. + * @return the converted value in the target unit. + * @throws IllegalArgumentException if inputUnit equals outputUnit. + * @throws NoSuchElementException if no conversion exists between the units. + */ public double convert(final String inputUnit, final String outputUnit, final double value) { if (inputUnit.equals(outputUnit)) { throw new IllegalArgumentException("inputUnit must be different from outputUnit."); @@ -81,6 +136,11 @@ public double convert(final String inputUnit, final String outputUnit, final dou return conversions.computeIfAbsent(conversionKey, k -> { throw new NoSuchElementException("No converter for: " + k); }).convert(value); } + /** + * Retrieves the set of all units supported by this converter. + * + * @return a set of available units. + */ public Set availableUnits() { return units; } diff --git a/src/main/java/com/thealgorithms/conversions/WordsToNumber.java b/src/main/java/com/thealgorithms/conversions/WordsToNumber.java new file mode 100644 index 000000000000..e2b81a0f4b47 --- /dev/null +++ b/src/main/java/com/thealgorithms/conversions/WordsToNumber.java @@ -0,0 +1,343 @@ +package com.thealgorithms.conversions; + +import java.io.Serial; +import java.math.BigDecimal; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + A Java-based utility for converting English word representations of numbers + into their numeric form. This utility supports whole numbers, decimals, + large values up to trillions, and even scientific notation where applicable. + It ensures accurate parsing while handling edge cases like negative numbers, + improper word placements, and ambiguous inputs. + * + */ + +public final class WordsToNumber { + + private WordsToNumber() { + } + + private enum NumberWord { + ZERO("zero", 0), + ONE("one", 1), + TWO("two", 2), + THREE("three", 3), + FOUR("four", 4), + FIVE("five", 5), + SIX("six", 6), + SEVEN("seven", 7), + EIGHT("eight", 8), + NINE("nine", 9), + TEN("ten", 10), + ELEVEN("eleven", 11), + TWELVE("twelve", 12), + THIRTEEN("thirteen", 13), + FOURTEEN("fourteen", 14), + FIFTEEN("fifteen", 15), + SIXTEEN("sixteen", 16), + SEVENTEEN("seventeen", 17), + EIGHTEEN("eighteen", 18), + NINETEEN("nineteen", 19), + TWENTY("twenty", 20), + THIRTY("thirty", 30), + FORTY("forty", 40), + FIFTY("fifty", 50), + SIXTY("sixty", 60), + SEVENTY("seventy", 70), + EIGHTY("eighty", 80), + NINETY("ninety", 90); + + private final String word; + private final int value; + + NumberWord(String word, int value) { + this.word = word; + this.value = value; + } + + public static Integer getValue(String word) { + for (NumberWord num : values()) { + if (word.equals(num.word)) { + return num.value; + } + } + return null; + } + } + + private enum PowerOfTen { + THOUSAND("thousand", new BigDecimal("1000")), + MILLION("million", new BigDecimal("1000000")), + BILLION("billion", new BigDecimal("1000000000")), + TRILLION("trillion", new BigDecimal("1000000000000")); + + private final String word; + private final BigDecimal value; + + PowerOfTen(String word, BigDecimal value) { + this.word = word; + this.value = value; + } + + public static BigDecimal getValue(String word) { + for (PowerOfTen power : values()) { + if (word.equals(power.word)) { + return power.value; + } + } + return null; + } + } + + public static String convert(String numberInWords) { + if (numberInWords == null) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.NULL_INPUT, ""); + } + + ArrayDeque wordDeque = preprocessWords(numberInWords); + BigDecimal completeNumber = convertWordQueueToBigDecimal(wordDeque); + + return completeNumber.toString(); + } + + public static BigDecimal convertToBigDecimal(String numberInWords) { + String conversionResult = convert(numberInWords); + return new BigDecimal(conversionResult); + } + + private static ArrayDeque preprocessWords(String numberInWords) { + String[] wordSplitArray = numberInWords.trim().split("[ ,-]"); + ArrayDeque wordDeque = new ArrayDeque<>(); + for (String word : wordSplitArray) { + if (word.isEmpty()) { + continue; + } + wordDeque.add(word.toLowerCase()); + } + if (wordDeque.isEmpty()) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.NULL_INPUT, ""); + } + return wordDeque; + } + + private static void handleConjunction(boolean prevNumWasHundred, boolean prevNumWasPowerOfTen, ArrayDeque wordDeque) { + if (wordDeque.isEmpty()) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.INVALID_CONJUNCTION, ""); + } + + String nextWord = wordDeque.pollFirst(); + String afterNextWord = wordDeque.peekFirst(); + + wordDeque.addFirst(nextWord); + + Integer number = NumberWord.getValue(nextWord); + + boolean isPrevWordValid = prevNumWasHundred || prevNumWasPowerOfTen; + boolean isNextWordValid = number != null && (number >= 10 || afterNextWord == null || "point".equals(afterNextWord)); + + if (!isPrevWordValid || !isNextWordValid) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.INVALID_CONJUNCTION, ""); + } + } + + private static BigDecimal handleHundred(BigDecimal currentChunk, String word, boolean prevNumWasPowerOfTen) { + boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0; + if (currentChunk.compareTo(BigDecimal.TEN) >= 0 || prevNumWasPowerOfTen) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word); + } + if (currentChunkIsZero) { + currentChunk = currentChunk.add(BigDecimal.ONE); + } + return currentChunk.multiply(BigDecimal.valueOf(100)); + } + + private static void handlePowerOfTen(List chunks, BigDecimal currentChunk, BigDecimal powerOfTen, String word, boolean prevNumWasPowerOfTen) { + boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0; + if (currentChunkIsZero || prevNumWasPowerOfTen) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word); + } + BigDecimal nextChunk = currentChunk.multiply(powerOfTen); + + if (!(chunks.isEmpty() || isAdditionSafe(chunks.getLast(), nextChunk))) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word); + } + chunks.add(nextChunk); + } + + private static BigDecimal handleNumber(Collection chunks, BigDecimal currentChunk, String word, Integer number) { + boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0; + if (number == 0 && !(currentChunkIsZero && chunks.isEmpty())) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word); + } + BigDecimal bigDecimalNumber = BigDecimal.valueOf(number); + + if (!currentChunkIsZero && !isAdditionSafe(currentChunk, bigDecimalNumber)) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word); + } + return currentChunk.add(bigDecimalNumber); + } + + private static void handlePoint(Collection chunks, BigDecimal currentChunk, ArrayDeque wordDeque) { + boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0; + if (!currentChunkIsZero) { + chunks.add(currentChunk); + } + + String decimalPart = convertDecimalPart(wordDeque); + chunks.add(new BigDecimal(decimalPart)); + } + + private static void handleNegative(boolean isNegative) { + if (isNegative) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.MULTIPLE_NEGATIVES, ""); + } + throw new WordsToNumberException(WordsToNumberException.ErrorType.INVALID_NEGATIVE, ""); + } + + private static BigDecimal convertWordQueueToBigDecimal(ArrayDeque wordDeque) { + BigDecimal currentChunk = BigDecimal.ZERO; + List chunks = new ArrayList<>(); + + boolean isNegative = "negative".equals(wordDeque.peek()); + if (isNegative) { + wordDeque.poll(); + } + + boolean prevNumWasHundred = false; + boolean prevNumWasPowerOfTen = false; + + while (!wordDeque.isEmpty()) { + String word = wordDeque.poll(); + + switch (word) { + case "and" -> { + handleConjunction(prevNumWasHundred, prevNumWasPowerOfTen, wordDeque); + continue; + } + case "hundred" -> { + currentChunk = handleHundred(currentChunk, word, prevNumWasPowerOfTen); + prevNumWasHundred = true; + continue; + } + default -> { + + } + } + prevNumWasHundred = false; + + BigDecimal powerOfTen = PowerOfTen.getValue(word); + if (powerOfTen != null) { + handlePowerOfTen(chunks, currentChunk, powerOfTen, word, prevNumWasPowerOfTen); + currentChunk = BigDecimal.ZERO; + prevNumWasPowerOfTen = true; + continue; + } + prevNumWasPowerOfTen = false; + + Integer number = NumberWord.getValue(word); + if (number != null) { + currentChunk = handleNumber(chunks, currentChunk, word, number); + continue; + } + + switch (word) { + case "point" -> { + handlePoint(chunks, currentChunk, wordDeque); + currentChunk = BigDecimal.ZERO; + continue; + } + case "negative" -> { + handleNegative(isNegative); + } + default -> { + + } + } + + throw new WordsToNumberException(WordsToNumberException.ErrorType.UNKNOWN_WORD, word); + } + + if (currentChunk.compareTo(BigDecimal.ZERO) != 0) { + chunks.add(currentChunk); + } + + BigDecimal completeNumber = combineChunks(chunks); + return isNegative ? completeNumber.multiply(BigDecimal.valueOf(-1)) + : + completeNumber; + } + + private static boolean isAdditionSafe(BigDecimal currentChunk, BigDecimal number) { + int chunkDigitCount = currentChunk.toString().length(); + int numberDigitCount = number.toString().length(); + return chunkDigitCount > numberDigitCount; + } + + private static String convertDecimalPart(ArrayDeque wordDeque) { + StringBuilder decimalPart = new StringBuilder("."); + + while (!wordDeque.isEmpty()) { + String word = wordDeque.poll(); + Integer number = NumberWord.getValue(word); + if (number == null) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD_AFTER_POINT, word); + } + decimalPart.append(number); + } + + boolean missingNumbers = decimalPart.length() == 1; + if (missingNumbers) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.MISSING_DECIMAL_NUMBERS, ""); + } + return decimalPart.toString(); + } + + private static BigDecimal combineChunks(List chunks) { + BigDecimal completeNumber = BigDecimal.ZERO; + for (BigDecimal chunk : chunks) { + completeNumber = completeNumber.add(chunk); + } + return completeNumber; + } + } + + class WordsToNumberException extends RuntimeException { + + @Serial private static final long serialVersionUID = 1L; + + enum ErrorType { + NULL_INPUT("'null' or empty input provided"), + UNKNOWN_WORD("Unknown Word: "), + UNEXPECTED_WORD("Unexpected Word: "), + UNEXPECTED_WORD_AFTER_POINT("Unexpected Word (after Point): "), + MISSING_DECIMAL_NUMBERS("Decimal part is missing numbers."), + MULTIPLE_NEGATIVES("Multiple 'Negative's detected."), + INVALID_NEGATIVE("Incorrect 'negative' placement"), + INVALID_CONJUNCTION("Incorrect 'and' placement"); + + private final String message; + + ErrorType(String message) { + this.message = message; + } + + public String formatMessage(String details) { + return "Invalid Input. " + message + (details.isEmpty() ? "" : details); + } + } + + public final ErrorType errorType; + + WordsToNumberException(ErrorType errorType, String details) { + super(errorType.formatMessage(details)); + this.errorType = errorType; + } + + public ErrorType getErrorType() { + return errorType; + } + } diff --git a/src/main/java/com/thealgorithms/datastructures/bags/Bag.java b/src/main/java/com/thealgorithms/datastructures/bags/Bag.java index 1bb143fabda1..afc3bbe40cce 100644 --- a/src/main/java/com/thealgorithms/datastructures/bags/Bag.java +++ b/src/main/java/com/thealgorithms/datastructures/bags/Bag.java @@ -4,14 +4,18 @@ import java.util.NoSuchElementException; /** - * A collection that allows adding and iterating over elements but does not support element removal. + * A generic collection that allows adding and iterating over elements but does not support + * element removal. This class implements a simple bag data structure, which can hold duplicate + * elements and provides operations to check for membership and the size of the collection. + * + *

Bag is not thread-safe and should not be accessed by multiple threads concurrently. * * @param the type of elements in this bag */ public class Bag implements Iterable { - private Node firstElement; // First element in the bag - private int size; // Number of elements in the bag + private Node firstElement; // Reference to the first element in the bag + private int size; // Count of elements in the bag // Node class representing each element in the bag private static final class Node { @@ -21,6 +25,7 @@ private static final class Node { /** * Constructs an empty bag. + *

This initializes the bag with zero elements. */ public Bag() { firstElement = null; @@ -30,7 +35,7 @@ public Bag() { /** * Checks if the bag is empty. * - * @return true if the bag is empty, false otherwise + * @return {@code true} if the bag contains no elements; {@code false} otherwise */ public boolean isEmpty() { return size == 0; @@ -39,7 +44,7 @@ public boolean isEmpty() { /** * Returns the number of elements in the bag. * - * @return the number of elements + * @return the number of elements currently in the bag */ public int size() { return size; @@ -48,7 +53,10 @@ public int size() { /** * Adds an element to the bag. * - * @param element the element to add + *

This method adds the specified element to the bag. Duplicates are allowed, and the + * bag will maintain the order in which elements are added. + * + * @param element the element to add; must not be {@code null} */ public void add(E element) { Node newNode = new Node<>(); @@ -61,8 +69,10 @@ public void add(E element) { /** * Checks if the bag contains a specific element. * - * @param element the element to check for - * @return true if the bag contains the element, false otherwise + *

This method uses the {@code equals} method of the element to determine membership. + * + * @param element the element to check for; must not be {@code null} + * @return {@code true} if the bag contains the specified element; {@code false} otherwise */ public boolean contains(E element) { for (E value : this) { @@ -76,6 +86,8 @@ public boolean contains(E element) { /** * Returns an iterator over the elements in this bag. * + *

The iterator provides a way to traverse the elements in the order they were added. + * * @return an iterator that iterates over the elements in the bag */ @Override @@ -88,19 +100,35 @@ private static class ListIterator implements Iterator { private Node currentElement; + /** + * Constructs a ListIterator starting from the given first element. + * + * @param firstElement the first element of the bag to iterate over + */ ListIterator(Node firstElement) { this.currentElement = firstElement; } + /** + * Checks if there are more elements to iterate over. + * + * @return {@code true} if there are more elements; {@code false} otherwise + */ @Override public boolean hasNext() { return currentElement != null; } + /** + * Returns the next element in the iteration. + * + * @return the next element in the bag + * @throws NoSuchElementException if there are no more elements to return + */ @Override public E next() { if (!hasNext()) { - throw new NoSuchElementException(); + throw new NoSuchElementException("No more elements in the bag."); } E element = currentElement.content; currentElement = currentElement.nextElement; diff --git a/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java b/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java index 33ea22c3d271..a2edd3db2d8e 100644 --- a/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java +++ b/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java @@ -4,6 +4,11 @@ /** * A generic BloomFilter implementation for probabilistic membership checking. + *

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

* * @param The type of elements to be stored in the Bloom filter. */ @@ -17,10 +22,14 @@ public class BloomFilter { * Constructs a BloomFilter with a specified number of hash functions and bit array size. * * @param numberOfHashFunctions the number of hash functions to use - * @param bitArraySize the size of the bit array + * @param bitArraySize the size of the bit array, which determines the capacity of the filter + * @throws IllegalArgumentException if numberOfHashFunctions or bitArraySize is less than 1 */ @SuppressWarnings("unchecked") public BloomFilter(int numberOfHashFunctions, int bitArraySize) { + if (numberOfHashFunctions < 1 || bitArraySize < 1) { + throw new IllegalArgumentException("Number of hash functions and bit array size must be greater than 0"); + } this.numberOfHashFunctions = numberOfHashFunctions; this.bitArray = new BitSet(bitArraySize); this.hashFunctions = new Hash[numberOfHashFunctions]; @@ -28,7 +37,7 @@ public BloomFilter(int numberOfHashFunctions, int bitArraySize) { } /** - * Initializes the hash functions with unique indices. + * Initializes the hash functions with unique indices to ensure different hashing. */ private void initializeHashFunctions() { for (int i = 0; i < numberOfHashFunctions; i++) { @@ -38,8 +47,12 @@ private void initializeHashFunctions() { /** * Inserts an element into the Bloom filter. + *

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

* - * @param key the element to insert + * @param key the element to insert into the Bloom filter */ public void insert(T key) { for (Hash hash : hashFunctions) { @@ -50,8 +63,13 @@ public void insert(T key) { /** * Checks if an element might be in the Bloom filter. + *

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

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

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

* * @param The type of elements to be hashed. */ @@ -76,7 +97,7 @@ private static class Hash { /** * Constructs a Hash function with a specified index. * - * @param index the index of this hash function + * @param index the index of this hash function, used to create a unique hash */ Hash(int index) { this.index = index; @@ -84,9 +105,13 @@ private static class Hash { /** * Computes the hash of the given key. + *

+ * The hash value is calculated by multiplying the index of the hash function + * with the ASCII sum of the string representation of the key. + *

* * @param key the element to hash - * @return the hash value + * @return the computed hash value */ public int compute(T key) { return index * asciiString(String.valueOf(key)); @@ -94,9 +119,13 @@ public int compute(T key) { /** * Computes the ASCII value sum of the characters in a string. + *

+ * This method iterates through each character of the string and accumulates + * their ASCII values to produce a single integer value. + *

* * @param word the string to compute - * @return the sum of ASCII values of the characters + * @return the sum of ASCII values of the characters in the string */ private int asciiString(String word) { int sum = 0; diff --git a/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java b/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java index 15e9a0956226..b709e16fd1f6 100644 --- a/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java +++ b/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java @@ -2,27 +2,62 @@ import java.util.concurrent.atomic.AtomicInteger; +/** + * The {@code CircularBuffer} class implements a generic circular (or ring) buffer. + * A circular buffer is a fixed-size data structure that operates in a FIFO (First In, First Out) manner. + * The buffer allows you to overwrite old data when the buffer is full and efficiently use limited memory. + * When the buffer is full, adding a new item will overwrite the oldest data. + * + * @param The type of elements stored in the circular buffer. + */ public class CircularBuffer { private final Item[] buffer; private final CircularPointer putPointer; private final CircularPointer getPointer; private final AtomicInteger size = new AtomicInteger(0); + /** + * Constructor to initialize the circular buffer with a specified size. + * + * @param size The size of the circular buffer. + * @throws IllegalArgumentException if the size is zero or negative. + */ public CircularBuffer(int size) { + if (size <= 0) { + throw new IllegalArgumentException("Buffer size must be positive"); + } // noinspection unchecked this.buffer = (Item[]) new Object[size]; this.putPointer = new CircularPointer(0, size); this.getPointer = new CircularPointer(0, size); } + /** + * Checks if the circular buffer is empty. + * This method is based on the current size of the buffer. + * + * @return {@code true} if the buffer is empty, {@code false} otherwise. + */ public boolean isEmpty() { return size.get() == 0; } + /** + * Checks if the circular buffer is full. + * The buffer is considered full when its size equals its capacity. + * + * @return {@code true} if the buffer is full, {@code false} otherwise. + */ public boolean isFull() { return size.get() == buffer.length; } + /** + * Retrieves and removes the item at the front of the buffer (FIFO). + * This operation will move the {@code getPointer} forward. + * + * @return The item at the front of the buffer, or {@code null} if the buffer is empty. + */ public Item get() { if (isEmpty()) { return null; @@ -33,31 +68,64 @@ public Item get() { return item; } + /** + * Adds an item to the end of the buffer (FIFO). + * If the buffer is full, this operation will overwrite the oldest data. + * + * @param item The item to be added. + * @throws IllegalArgumentException if the item is null. + * @return {@code true} if the item was successfully added, {@code false} if the buffer was full and the item overwrote existing data. + */ public boolean put(Item item) { + if (item == null) { + throw new IllegalArgumentException("Null items are not allowed"); + } + + boolean wasEmpty = isEmpty(); if (isFull()) { - return false; + getPointer.getAndIncrement(); // Move get pointer to discard oldest item + } else { + size.incrementAndGet(); } buffer[putPointer.getAndIncrement()] = item; - size.incrementAndGet(); - return true; + return wasEmpty; } + /** + * The {@code CircularPointer} class is a helper class used to track the current index (pointer) + * in the circular buffer. + * The max value represents the capacity of the buffer. + * The `CircularPointer` class ensures that the pointer automatically wraps around to 0 + * when it reaches the maximum index. + * This is achieved in the `getAndIncrement` method, where the pointer + * is incremented and then taken modulo the maximum value (`max`). + * This operation ensures that the pointer always stays within the bounds of the buffer. + */ private static class CircularPointer { private int pointer; private final int max; + /** + * Constructor to initialize the circular pointer. + * + * @param pointer The initial position of the pointer. + * @param max The maximum size (capacity) of the circular buffer. + */ CircularPointer(int pointer, int max) { this.pointer = pointer; this.max = max; } + /** + * Increments the pointer by 1 and wraps it around to 0 if it reaches the maximum value. + * This ensures the pointer always stays within the buffer's bounds. + * + * @return The current pointer value before incrementing. + */ public int getAndIncrement() { - if (pointer == max) { - pointer = 0; - } int tmp = pointer; - pointer++; + pointer = (pointer + 1) % max; return tmp; } } diff --git a/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java b/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java index 4e233224e367..f0d8ea8f7ff3 100644 --- a/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java +++ b/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java @@ -6,16 +6,21 @@ /** * The {@code LFUCache} class implements a Least Frequently Used (LFU) cache. * An LFU cache evicts the least frequently used item when the cache reaches its capacity. - * It keeps track of how many times each item is used and maintains a doubly linked list - * for efficient addition and removal of items based on their frequency of use. + * It maintains a mapping of keys to nodes, where each node contains the key, its associated value, + * and a frequency count that tracks how many times the item has been accessed. A doubly linked list + * is used to efficiently manage the ordering of items based on their usage frequency. * - * @param The type of keys maintained by this cache. - * @param The type of mapped values. + *

This implementation is designed to provide O(1) time complexity for both the {@code get} and + * {@code put} operations, which is achieved through the use of a hashmap for quick access and a + * doubly linked list for maintaining the order of item frequencies.

* *

* Reference: LFU Cache - Wikipedia *

* + * @param The type of keys maintained by this cache. + * @param The type of mapped values. + * * @author Akshay Dubey (https://github.com/itsAkshayDubey) */ public class LFUCache { @@ -75,7 +80,7 @@ public LFUCache(int capacity) { /** * Retrieves the value associated with the given key from the cache. - * If the key exists, the node's frequency is increased and the node is repositioned + * If the key exists, the node's frequency is incremented, and the node is repositioned * in the linked list based on its updated frequency. * * @param key The key whose associated value is to be returned. diff --git a/src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java b/src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java index 97818ff83351..ec39d2a6ed28 100644 --- a/src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java +++ b/src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java @@ -4,15 +4,40 @@ import java.util.Map; /** - * Least recently used (LRU) - *

- * Discards the least recently used items first. This algorithm requires keeping - * track of what was used when, which is expensive if one wants to make sure the - * algorithm always discards the least recently used item. - * https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU) + * A Least Recently Used (LRU) Cache implementation. * - * @param key type - * @param value type + *

An LRU cache is a fixed-size cache that maintains items in order of use. When the cache reaches + * its capacity and a new item needs to be added, it removes the least recently used item first. + * This implementation provides O(1) time complexity for both get and put operations.

+ * + *

Features:

+ *
    + *
  • Fixed-size cache with configurable capacity
  • + *
  • Constant time O(1) operations for get and put
  • + *
  • Thread-unsafe - should be externally synchronized if used in concurrent environments
  • + *
  • Supports null values but not null keys
  • + *
+ * + *

Implementation Details:

+ *
    + *
  • Uses a HashMap for O(1) key-value lookups
  • + *
  • Maintains a doubly-linked list for tracking access order
  • + *
  • The head of the list contains the least recently used item
  • + *
  • The tail of the list contains the most recently used item
  • + *
+ * + *

Example usage:

+ *
+ * LRUCache cache = new LRUCache<>(3); // Create cache with capacity 3
+ * cache.put("A", 1); // Cache: A=1
+ * cache.put("B", 2); // Cache: A=1, B=2
+ * cache.put("C", 3); // Cache: A=1, B=2, C=3
+ * cache.get("A");    // Cache: B=2, C=3, A=1 (A moved to end)
+ * cache.put("D", 4); // Cache: C=3, A=1, D=4 (B evicted)
+ * 
+ * + * @param the type of keys maintained by this cache + * @param the type of mapped values */ public class LRUCache { @@ -30,6 +55,11 @@ public LRUCache(int cap) { setCapacity(cap); } + /** + * Returns the current capacity of the cache. + * + * @param newCapacity the new capacity of the cache + */ private void setCapacity(int newCapacity) { checkCapacity(newCapacity); for (int i = data.size(); i > newCapacity; i--) { @@ -39,6 +69,11 @@ private void setCapacity(int newCapacity) { this.cap = newCapacity; } + /** + * Evicts the least recently used item from the cache. + * + * @return the evicted entry + */ private Entry evict() { if (head == null) { throw new RuntimeException("cache cannot be empty!"); @@ -50,12 +85,25 @@ private Entry evict() { return evicted; } + /** + * Checks if the capacity is valid. + * + * @param capacity the capacity to check + */ private void checkCapacity(int capacity) { if (capacity <= 0) { throw new RuntimeException("capacity must greater than 0!"); } } + /** + * Returns the value to which the specified key is mapped, or null if this cache contains no + * mapping for the key. + * + * @param key the key whose associated value is to be returned + * @return the value to which the specified key is mapped, or null if this cache contains no + * mapping for the key + */ public V get(K key) { if (!data.containsKey(key)) { return null; @@ -65,6 +113,11 @@ public V get(K key) { return entry.getValue(); } + /** + * Moves the specified entry to the end of the list. + * + * @param entry the entry to move + */ private void moveNodeToLast(Entry entry) { if (tail == entry) { return; @@ -86,6 +139,12 @@ private void moveNodeToLast(Entry entry) { tail = entry; } + /** + * Associates the specified value with the specified key in this cache. + * + * @param key the key with which the specified value is to be associated + * @param value the value to be associated with the specified key + */ public void put(K key, V value) { if (data.containsKey(key)) { final Entry existingEntry = data.get(key); @@ -107,6 +166,11 @@ public void put(K key, V value) { data.put(key, newEntry); } + /** + * Adds a new entry to the end of the list. + * + * @param newEntry the entry to add + */ private void addNewEntry(Entry newEntry) { if (data.isEmpty()) { head = newEntry; diff --git a/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java b/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java index 9c155be8b195..93b13e6ad654 100644 --- a/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java +++ b/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java @@ -4,14 +4,17 @@ import java.util.Map; /** - * Most recently used (MRU) + * Represents a Most Recently Used (MRU) Cache. *

- * In contrast to Least Recently Used (LRU), MRU discards the most recently used - * items first. - * https://en.wikipedia.org/wiki/Cache_replacement_policies#Most_recently_used_(MRU) + * In contrast to the Least Recently Used (LRU) strategy, the MRU caching policy + * evicts the most recently accessed items first. This class provides methods to + * store key-value pairs and manage cache eviction based on this policy. * - * @param key type - * @param value type + * For more information, refer to: + * MRU on Wikipedia. + * + * @param the type of keys maintained by this cache + * @param the type of values associated with the keys */ public class MRUCache { @@ -21,40 +24,74 @@ public class MRUCache { private int cap; private static final int DEFAULT_CAP = 100; + /** + * Creates an MRUCache with the default capacity. + */ public MRUCache() { setCapacity(DEFAULT_CAP); } + /** + * Creates an MRUCache with a specified capacity. + * + * @param cap the maximum number of items the cache can hold + */ + public MRUCache(int cap) { + setCapacity(cap); + } + + /** + * Sets the capacity of the cache and evicts items if the new capacity + * is less than the current number of items. + * + * @param newCapacity the new capacity to set + */ private void setCapacity(int newCapacity) { checkCapacity(newCapacity); - for (int i = data.size(); i > newCapacity; i--) { + while (data.size() > newCapacity) { Entry evicted = evict(); data.remove(evicted.getKey()); } this.cap = newCapacity; } + /** + * Checks if the specified capacity is valid. + * + * @param capacity the capacity to check + * @throws IllegalArgumentException if the capacity is less than or equal to zero + */ private void checkCapacity(int capacity) { if (capacity <= 0) { - throw new RuntimeException("capacity must greater than 0!"); + throw new IllegalArgumentException("Capacity must be greater than 0!"); } } + /** + * Evicts the most recently used entry from the cache. + * + * @return the evicted entry + * @throws RuntimeException if the cache is empty + */ private Entry evict() { if (head == null) { - throw new RuntimeException("cache cannot be empty!"); + throw new RuntimeException("Cache cannot be empty!"); } final Entry evicted = this.tail; tail = evicted.getPreEntry(); - tail.setNextEntry(null); + if (tail != null) { + tail.setNextEntry(null); + } evicted.setNextEntry(null); return evicted; } - public MRUCache(int cap) { - setCapacity(cap); - } - + /** + * Retrieves the value associated with the specified key. + * + * @param key the key whose associated value is to be returned + * @return the value associated with the specified key, or null if the key does not exist + */ public V get(K key) { if (!data.containsKey(key)) { return null; @@ -64,11 +101,19 @@ public V get(K key) { return entry.getValue(); } + /** + * Associates the specified value with the specified key in the cache. + * If the key already exists, its value is updated and the entry is moved to the most recently used position. + * If the cache is full, the most recently used entry is evicted before adding the new entry. + * + * @param key the key with which the specified value is to be associated + * @param value the value to be associated with the specified key + */ public void put(K key, V value) { if (data.containsKey(key)) { - final Entry exitingEntry = data.get(key); - exitingEntry.setValue(value); - moveEntryToLast(exitingEntry); + final Entry existingEntry = data.get(key); + existingEntry.setValue(value); + moveEntryToLast(existingEntry); return; } Entry newEntry; @@ -84,6 +129,11 @@ public void put(K key, V value) { data.put(key, newEntry); } + /** + * Adds a new entry to the cache and updates the head and tail pointers accordingly. + * + * @param newEntry the new entry to be added + */ private void addNewEntry(Entry newEntry) { if (data.isEmpty()) { head = newEntry; @@ -96,6 +146,11 @@ private void addNewEntry(Entry newEntry) { tail = newEntry; } + /** + * Moves the specified entry to the most recently used position in the cache. + * + * @param entry the entry to be moved + */ private void moveEntryToLast(Entry entry) { if (tail == entry) { return; @@ -117,8 +172,14 @@ private void moveEntryToLast(Entry entry) { tail = entry; } + /** + * A nested class representing an entry in the cache, which holds a key-value pair + * and references to the previous and next entries in the linked list structure. + * + * @param the type of the key + * @param the type of the value + */ static final class Entry { - private Entry preEntry; private Entry nextEntry; private I key; diff --git a/src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java b/src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java index 2c6ce8a427d1..d33bd3ee84d9 100644 --- a/src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java +++ b/src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java @@ -1,53 +1,33 @@ package com.thealgorithms.datastructures.crdt; +import java.time.Instant; import java.util.HashMap; import java.util.Map; /** - * Last-Write-Wins Element Set (LWWElementSet) is a state-based CRDT (Conflict-free Replicated Data Type) - * designed for managing sets in a distributed and concurrent environment. It supports the addition and removal - * of elements, using timestamps to determine the order of operations. The set is split into two subsets: - * the add set for elements to be added and the remove set for elements to be removed. + * Last-Write-Wins Element Set (LWWElementSet) is a state-based CRDT (Conflict-free Replicated Data + * Type) designed for managing sets in a distributed and concurrent environment. It supports the + * addition and removal of elements, using timestamps to determine the order of operations. The set + * is split into two subsets: the add set for elements to be added and the remove set for elements + * to be removed. The LWWElementSet ensures that the most recent operation (based on the timestamp) + * wins in the case of concurrent operations. * - * @author itakurah (Niklas Hoefflin) (https://github.com/itakurah) - * @see Conflict-free_replicated_data_type - * @see itakurah (Niklas Hoefflin) + * @param The type of the elements in the LWWElementSet. + * @author itakurah (GitHub), Niklas Hoefflin (LinkedIn) + * @see Conflict free + * replicated data type (Wikipedia) + * @see A comprehensive study of + * Convergent and Commutative Replicated Data Types */ - -class Element { - String key; - int timestamp; - Bias bias; +class LWWElementSet { + final Map> addSet; + final Map> removeSet; /** - * Constructs a new Element with the specified key, timestamp and bias. - * - * @param key The key of the element. - * @param timestamp The timestamp associated with the element. - * @param bias The bias of the element (ADDS or REMOVALS). - */ - Element(String key, int timestamp, Bias bias) { - this.key = key; - this.timestamp = timestamp; - this.bias = bias; - } -} - -enum Bias { - /** - * ADDS bias for the add set. - * REMOVALS bias for the remove set. - */ - ADDS, - REMOVALS -} - -class LWWElementSet { - private final Map addSet; - private final Map removeSet; - - /** - * Constructs an empty LWWElementSet. + * Constructs an empty LWWElementSet. This constructor initializes the addSet and removeSet as + * empty HashMaps. The addSet stores elements that are added, and the removeSet stores elements + * that are removed. */ LWWElementSet() { this.addSet = new HashMap<>(); @@ -55,84 +35,92 @@ class LWWElementSet { } /** - * Adds an element to the addSet. + * Adds an element to the addSet with the current timestamp. This method stores the element in the + * addSet, ensuring that the element is added to the set with an associated timestamp that + * represents the time of the addition. * - * @param e The element to be added. + * @param key The key of the element to be added. */ - public void add(Element e) { - addSet.put(e.key, e); + public void add(T key) { + addSet.put(key, new Element<>(key, Instant.now())); } /** - * Removes an element from the removeSet. + * Removes an element by adding it to the removeSet with the current timestamp. This method adds + * the element to the removeSet, marking it as removed with the current timestamp. * - * @param e The element to be removed. + * @param key The key of the element to be removed. */ - public void remove(Element e) { - if (lookup(e)) { - removeSet.put(e.key, e); - } + public void remove(T key) { + removeSet.put(key, new Element<>(key, Instant.now())); } /** - * Checks if an element is in the LWWElementSet by comparing timestamps in the addSet and removeSet. + * Checks if an element is in the LWWElementSet. An element is considered present if it exists in + * the addSet and either does not exist in the removeSet, or its add timestamp is later than any + * corresponding remove timestamp. * - * @param e The element to be checked. - * @return True if the element is present, false otherwise. + * @param key The key of the element to be checked. + * @return {@code true} if the element is present in the set (i.e., its add timestamp is later + * than its remove timestamp, or it is not in the remove set), {@code false} otherwise (i.e., + * the element has been removed or its remove timestamp is later than its add timestamp). */ - public boolean lookup(Element e) { - Element inAddSet = addSet.get(e.key); - Element inRemoveSet = removeSet.get(e.key); + public boolean lookup(T key) { + Element inAddSet = addSet.get(key); + Element inRemoveSet = removeSet.get(key); - return (inAddSet != null && (inRemoveSet == null || inAddSet.timestamp > inRemoveSet.timestamp)); + return inAddSet != null && (inRemoveSet == null || inAddSet.timestamp.isAfter(inRemoveSet.timestamp)); } /** - * Compares the LWWElementSet with another LWWElementSet to check if addSet and removeSet are a subset. + * Merges another LWWElementSet into this set. This method takes the union of both the add-sets + * and remove-sets from the two sets, resolving conflicts by keeping the element with the latest + * timestamp. If an element appears in both the add-set and remove-set of both sets, the one with + * the later timestamp will be retained. * - * @param other The LWWElementSet to compare. - * @return True if the set is subset, false otherwise. + * @param other The LWWElementSet to merge with the current set. */ - public boolean compare(LWWElementSet other) { - return other.addSet.keySet().containsAll(addSet.keySet()) && other.removeSet.keySet().containsAll(removeSet.keySet()); + public void merge(LWWElementSet other) { + for (Map.Entry> entry : other.addSet.entrySet()) { + addSet.merge(entry.getKey(), entry.getValue(), this::resolveConflict); + } + for (Map.Entry> entry : other.removeSet.entrySet()) { + removeSet.merge(entry.getKey(), entry.getValue(), this::resolveConflict); + } } /** - * Merges another LWWElementSet into this set by resolving conflicts based on timestamps. + * Resolves conflicts between two elements by selecting the one with the later timestamp. This + * method is used when merging two LWWElementSets to ensure that the most recent operation (based + * on timestamps) is kept. * - * @param other The LWWElementSet to merge. + * @param e1 The first element. + * @param e2 The second element. + * @return The element with the later timestamp. */ - public void merge(LWWElementSet other) { - for (Element e : other.addSet.values()) { - if (!addSet.containsKey(e.key) || compareTimestamps(addSet.get(e.key), e)) { - addSet.put(e.key, e); - } - } - - for (Element e : other.removeSet.values()) { - if (!removeSet.containsKey(e.key) || compareTimestamps(removeSet.get(e.key), e)) { - removeSet.put(e.key, e); - } - } + private Element resolveConflict(Element e1, Element e2) { + return e1.timestamp.isAfter(e2.timestamp) ? e1 : e2; } +} + +/** + * Represents an element in the LWWElementSet, consisting of a key and a timestamp. This class is + * used to store the elements in both the add and remove sets with their respective timestamps. + * + * @param The type of the key associated with the element. + */ +class Element { + T key; + Instant timestamp; /** - * Compares timestamps of two elements based on their bias (ADDS or REMOVALS). + * Constructs a new Element with the specified key and timestamp. * - * @param e The first element. - * @param other The second element. - * @return True if the first element's timestamp is greater or the bias is ADDS and timestamps are equal. + * @param key The key of the element. + * @param timestamp The timestamp associated with the element. */ - public boolean compareTimestamps(Element e, Element other) { - if (e.bias != other.bias) { - throw new IllegalArgumentException("Invalid bias value"); - } - Bias bias = e.bias; - int timestampComparison = Integer.compare(e.timestamp, other.timestamp); - - if (timestampComparison == 0) { - return bias != Bias.ADDS; - } - return timestampComparison < 0; + Element(T key, Instant timestamp) { + this.key = key; + this.timestamp = timestamp; } } diff --git a/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java b/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java index a5fa9cbe94e7..cd5dc580b694 100644 --- a/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java +++ b/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java @@ -10,21 +10,24 @@ import java.util.stream.StreamSupport; /** - * This class implements a dynamic array. + * This class implements a dynamic array, which can grow or shrink in size + * as elements are added or removed. It provides an array-like interface + * with methods to add, remove, and access elements, along with iterators + * to traverse the elements. * - * @param the type that each index of the array will hold + * @param the type of elements that this array can hold */ public class DynamicArray implements Iterable { private static final int DEFAULT_CAPACITY = 16; private int size; - private int modCount; // Tracks structural modifications for the iterator + private int modCount; // Tracks structural modifications for iterator integrity private Object[] elements; /** - * Constructor with initial capacity. + * Constructs a new DynamicArray with the specified initial capacity. * - * @param capacity the starting length of the desired array + * @param capacity the initial capacity of the array * @throws IllegalArgumentException if the specified capacity is negative */ public DynamicArray(final int capacity) { @@ -37,14 +40,15 @@ public DynamicArray(final int capacity) { } /** - * No-args constructor with default capacity. + * Constructs a new DynamicArray with a default initial capacity. */ public DynamicArray() { this(DEFAULT_CAPACITY); } /** - * Adds an element to the array. If full, creates a new array with double the size. + * Adds an element to the end of the array. If the array is full, it + * creates a new array with double the size to accommodate the new element. * * @param element the element to be added to the array */ @@ -55,11 +59,11 @@ public void add(final E element) { } /** - * Places an element at the desired index, expanding capacity if necessary. + * Places an element at the specified index, expanding capacity if necessary. * - * @param index the index for the element to be placed - * @param element the element to be inserted - * @throws IndexOutOfBoundsException if n is less than 0 or greater or equal to the number of elements in the array + * @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 */ public void put(final int index, E element) { if (index < 0) { @@ -74,11 +78,11 @@ public void put(final int index, E element) { } /** - * Gets the element at a given index. + * Retrieves the element at the specified index. * - * @param index the desired index of the element + * @param index the index of the element to retrieve * @return the element at the specified index - * @throws IndexOutOfBoundsException if n is less than 0 or greater or equal to the number of elements in the array + * @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) { @@ -89,11 +93,11 @@ public E get(final int index) { } /** - * Removes an element from the array. + * Removes and returns the element at the specified index. * * @param index the index of the element to be removed - * @return the element removed - * @throws IndexOutOfBoundsException if n is less than 0 or greater or equal to the number of elements in the array + * @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 */ public E remove(final int index) { if (index < 0 || index >= size) { @@ -106,16 +110,16 @@ public E remove(final int index) { } /** - * Gets the size of the array. + * Returns the current number of elements in the array. * - * @return the size + * @return the number of elements in the array */ public int getSize() { return size; } /** - * Checks if the array is empty. + * Checks whether the array is empty. * * @return true if the array contains no elements, false otherwise */ @@ -123,10 +127,20 @@ public boolean isEmpty() { return size == 0; } + /** + * Returns a sequential stream with this collection as its source. + * + * @return a stream of the elements in the array + */ public Stream stream() { return StreamSupport.stream(spliterator(), false); } + /** + * Ensures that the array has enough capacity to hold the specified number of elements. + * + * @param minCapacity the minimum capacity required + */ private void ensureCapacity(int minCapacity) { if (minCapacity > elements.length) { int newCapacity = Math.max(elements.length * 2, minCapacity); @@ -134,6 +148,12 @@ 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. + * + * @param index the index of the element to remove + */ private void fastRemove(int index) { int numMoved = size - index - 1; if (numMoved > 0) { @@ -142,31 +162,58 @@ private void fastRemove(int index) { elements[--size] = null; // Clear to let GC do its work } + /** + * Returns a string representation of the array, including only the elements that are currently stored. + * + * @return a string containing the elements in the array + */ @Override public String toString() { return Arrays.toString(Arrays.copyOf(elements, size)); } + /** + * Returns an iterator over the elements in this array in proper sequence. + * + * @return an Iterator over the elements in the array + */ @Override public Iterator iterator() { return new DynamicArrayIterator(); } + /** + * Private iterator class for the DynamicArray. + */ private final class DynamicArrayIterator implements Iterator { private int cursor; private int expectedModCount; + /** + * Constructs a new iterator for the dynamic array. + */ DynamicArrayIterator() { this.expectedModCount = modCount; } + /** + * Checks if there are more elements in the iteration. + * + * @return true if there are more elements, false otherwise + */ @Override public boolean hasNext() { checkForComodification(); return cursor < size; } + /** + * Returns the next element in the iteration. + * + * @return the next element in the iteration + * @throws NoSuchElementException if the iteration has no more elements + */ @Override @SuppressWarnings("unchecked") public E next() { @@ -177,22 +224,38 @@ public E next() { return (E) elements[cursor++]; } + /** + * 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 + */ @Override public void remove() { if (cursor <= 0) { - throw new IllegalStateException(); + throw new IllegalStateException("Cannot remove element before calling next()"); } checkForComodification(); DynamicArray.this.remove(--cursor); - expectedModCount = ++modCount; + expectedModCount = modCount; } + /** + * Checks for concurrent modifications to the array during iteration. + * + * @throws ConcurrentModificationException if the array has been modified structurally + */ private void checkForComodification() { if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } } + /** + * 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 + */ @Override public void forEachRemaining(Consumer action) { Objects.requireNonNull(action); diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java b/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java index 54fb5fba5c1b..741caa59c5b5 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java @@ -1,25 +1,26 @@ -/* - Time Complexity = O(E), where E is equal to the number of edges - */ package com.thealgorithms.datastructures.graphs; import java.util.ArrayList; -import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.PriorityQueue; +/** + * AStar class implements the A* pathfinding algorithm to find the shortest path in a graph. + * The graph is represented using an adjacency list, and the algorithm uses a heuristic to estimate + * the cost to reach the destination node. + * Time Complexity = O(E), where E is equal to the number of edges + */ public final class AStar { private AStar() { } - private static class Graph { - - // Graph's structure can be changed only applying changes to this class. - + /** + * Represents a graph using an adjacency list. + */ + static class Graph { private ArrayList> graph; - // Initialise ArrayLists in Constructor Graph(int size) { this.graph = new ArrayList<>(); for (int i = 0; i < size; i++) { @@ -31,15 +32,17 @@ private ArrayList getNeighbours(int from) { return this.graph.get(from); } - // Graph is bidirectional, for just one direction remove second instruction of this method. + // Add a bidirectional edge to the graph private void addEdge(Edge edge) { this.graph.get(edge.getFrom()).add(new Edge(edge.getFrom(), edge.getTo(), edge.getWeight())); this.graph.get(edge.getTo()).add(new Edge(edge.getTo(), edge.getFrom(), edge.getWeight())); } } + /** + * Represents an edge in the graph with a start node, end node, and weight. + */ private static class Edge { - private int from; private int to; private int weight; @@ -63,12 +66,13 @@ public int getWeight() { } } - // class to iterate during the algorithm execution, and also used to return the solution. - private static class PathAndDistance { - - private int distance; // distance advanced so far. - private ArrayList path; // list of visited nodes in this path. - private int estimated; // heuristic value associated to the last node od the path (current node). + /** + * Contains information about the path and its total distance. + */ + static class PathAndDistance { + private int distance; // total distance from the start node + private ArrayList path; // list of nodes in the path + private int estimated; // heuristic estimate for reaching the destination PathAndDistance(int distance, ArrayList path, int estimated) { this.distance = distance; @@ -87,112 +91,54 @@ public ArrayList getPath() { public int getEstimated() { return estimated; } - - private void printSolution() { - if (this.path != null) { - System.out.println("Optimal path: " + this.path + ", distance: " + this.distance); - } else { - System.out.println("There is no path available to connect the points"); - } - } } - private static void initializeGraph(Graph graph, ArrayList data) { + // Initializes the graph with edges defined in the input data + static void initializeGraph(Graph graph, List data) { for (int i = 0; i < data.size(); i += 4) { graph.addEdge(new Edge(data.get(i), data.get(i + 1), data.get(i + 2))); } - /* - .x. node - (y) cost - - or | or / bidirectional connection - - ( 98)- .7. -(86)- .4. - | - ( 85)- .17. -(142)- .18. -(92)- .8. -(87)- .11. - | - . 1. -------------------- (160) - | \ | - (211) \ .6. - | \ | - . 5. (101)-.13. -(138) (115) - | | | / - ( 99) ( 97) | / - | | | / - .12. -(151)- .15. -(80)- .14. | / - | | | | / - ( 71) (140) (146)- .2. -(120) - | | | - .19. -( 75)- . 0. .10. -(75)- .3. - | | - (118) ( 70) - | | - .16. -(111)- .9. - */ - } - - public static void main(String[] args) { - // heuristic function optimistic values - int[] heuristic = { - 366, - 0, - 160, - 242, - 161, - 178, - 77, - 151, - 226, - 244, - 241, - 234, - 380, - 98, - 193, - 253, - 329, - 80, - 199, - 374, - }; - - Graph graph = new Graph(20); - ArrayList graphData = new ArrayList<>(Arrays.asList(0, 19, 75, null, 0, 15, 140, null, 0, 16, 118, null, 19, 12, 71, null, 12, 15, 151, null, 16, 9, 111, null, 9, 10, 70, null, 10, 3, 75, null, 3, 2, 120, null, 2, 14, 146, null, 2, 13, 138, null, 2, 6, 115, null, 15, 14, 80, null, - 15, 5, 99, null, 14, 13, 97, null, 5, 1, 211, null, 13, 1, 101, null, 6, 1, 160, null, 1, 17, 85, null, 17, 7, 98, null, 7, 4, 86, null, 17, 18, 142, null, 18, 8, 92, null, 8, 11, 87)); - initializeGraph(graph, graphData); - - PathAndDistance solution = aStar(3, 1, graph, heuristic); - solution.printSolution(); } + /** + * Implements the A* pathfinding algorithm to find the shortest path from a start node to a destination node. + * + * @param from the starting node + * @param to the destination node + * @param graph the graph representation of the problem + * @param heuristic the heuristic estimates for each node + * @return a PathAndDistance object containing the shortest path and its distance + */ public static PathAndDistance aStar(int from, int to, Graph graph, int[] heuristic) { - // nodes are prioritised by the less value of the current distance of their paths, and the - // estimated value - // given by the heuristic function to reach the destination point from the current point. + // PriorityQueue to explore nodes based on their distance and estimated cost to reach the destination PriorityQueue queue = new PriorityQueue<>(Comparator.comparingInt(a -> (a.getDistance() + a.getEstimated()))); - // dummy data to start the algorithm from the beginning point - queue.add(new PathAndDistance(0, new ArrayList<>(List.of(from)), 0)); + // Start with the initial node + queue.add(new PathAndDistance(0, new ArrayList<>(List.of(from)), heuristic[from])); boolean solutionFound = false; PathAndDistance currentData = new PathAndDistance(-1, null, -1); + while (!queue.isEmpty() && !solutionFound) { - currentData = queue.poll(); // first in the queue, best node so keep exploring. - int currentPosition = currentData.getPath().get(currentData.getPath().size() - 1); // current node. + currentData = queue.poll(); // get the best node from the queue + int currentPosition = currentData.getPath().get(currentData.getPath().size() - 1); // current node + + // Check if the destination has been reached if (currentPosition == to) { solutionFound = true; } else { for (Edge edge : graph.getNeighbours(currentPosition)) { - if (!currentData.getPath().contains(edge.getTo())) { // Avoid Cycles + // Avoid cycles by checking if the next node is already in the path + if (!currentData.getPath().contains(edge.getTo())) { ArrayList updatedPath = new ArrayList<>(currentData.getPath()); - updatedPath.add(edge.getTo()); // Add the new node to the path, update the distance, - // and the heuristic function value associated to that path. + updatedPath.add(edge.getTo()); + + // Update the distance and heuristic for the new path queue.add(new PathAndDistance(currentData.getDistance() + edge.getWeight(), updatedPath, heuristic[edge.getTo()])); } } } } return (solutionFound) ? currentData : new PathAndDistance(-1, null, -1); - // Out of while loop, if there is a solution, the current Data stores the optimal path, and - // its distance } } diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java b/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java index b67c5512e622..aea2b74bd13b 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java @@ -6,7 +6,6 @@ class Cycle { private final int nodes; - private final int edges; private int[][] adjacencyMatrix; private boolean[] visited; ArrayList> cycles = new ArrayList>(); @@ -16,7 +15,7 @@ class Cycle { System.out.print("Enter the no. of nodes: "); nodes = in.nextInt(); System.out.print("Enter the no. of Edges: "); - edges = in.nextInt(); + final int edges = in.nextInt(); adjacencyMatrix = new int[nodes][nodes]; visited = new boolean[nodes]; diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java new file mode 100644 index 000000000000..a686b808a970 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java @@ -0,0 +1,66 @@ +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/EdmondsBlossomAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithm.java index 27ad96d71876..db716580d689 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithm.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithm.java @@ -30,7 +30,7 @@ private EdmondsBlossomAlgorithm() { * @param vertexCount The number of vertices in the graph. * @return A list of matched pairs of vertices. */ - public static List maximumMatching(List edges, int vertexCount) { + public static List maximumMatching(Iterable edges, int vertexCount) { List> graph = new ArrayList<>(vertexCount); // Initialize each vertex's adjacency list. diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/FloydWarshall.java b/src/main/java/com/thealgorithms/datastructures/graphs/FloydWarshall.java index d47ffe3aa27d..e5e673a21794 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/FloydWarshall.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/FloydWarshall.java @@ -1,42 +1,75 @@ package com.thealgorithms.datastructures.graphs; -import java.util.Scanner; - +/** + * The {@code FloydWarshall} class provides an implementation of the Floyd-Warshall algorithm + * to compute the shortest paths between all pairs of vertices in a weighted graph. + * It handles both positive and negative edge weights but does not support negative cycles. + * The algorithm is based on dynamic programming and runs in O(V^3) time complexity, + * where V is the number of vertices in the graph. + * + *

+ * The distance matrix is updated iteratively to find the shortest distance between any two vertices + * by considering each vertex as an intermediate step. + *

+ * + * Reference: Floyd-Warshall Algorithm + */ public class FloydWarshall { private int[][] distanceMatrix; - private int numberofvertices; // number of vertices in the graph + private int numberofvertices; public static final int INFINITY = 999; + /** + * Constructs a Floyd-Warshall instance for a graph with the given number of vertices. + * Initializes the distance matrix for the graph. + * + * @param numberofvertices The number of vertices in the graph. + */ public FloydWarshall(int numberofvertices) { - distanceMatrix = new int[numberofvertices + 1][numberofvertices + 1]; // stores the value of distance from all the possible path form the source - // vertex to destination vertex + distanceMatrix = new int[numberofvertices + 1][numberofvertices + 1]; // The matrix is initialized with 0's by default this.numberofvertices = numberofvertices; } - public void floydwarshall(int[][] adjacencyMatrix) { // calculates all the distances from source to destination vertex + /** + * Executes the Floyd-Warshall algorithm to compute the shortest path between all pairs of vertices. + * It uses an adjacency matrix to calculate the distance matrix by considering each vertex as an intermediate point. + * + * @param adjacencyMatrix The weighted adjacency matrix representing the graph. + * A value of 0 means no direct edge between the vertices, except for diagonal elements which are 0 (distance to self). + */ + public void floydwarshall(int[][] adjacencyMatrix) { + // Initialize the distance matrix with the adjacency matrix. for (int source = 1; source <= numberofvertices; source++) { - for (int destination = 1; destination <= numberofvertices; destination++) { - distanceMatrix[source][destination] = adjacencyMatrix[source][destination]; - } + System.arraycopy(adjacencyMatrix[source], 1, distanceMatrix[source], 1, numberofvertices); } for (int intermediate = 1; intermediate <= numberofvertices; intermediate++) { for (int source = 1; source <= numberofvertices; source++) { for (int destination = 1; destination <= numberofvertices; destination++) { - if (distanceMatrix[source][intermediate] + distanceMatrix[intermediate][destination] < distanceMatrix[source][destination]) { // calculated distance it get replaced as - // new shortest distance // if the new - // distance calculated is less then the - // earlier shortest + // Update distance if a shorter path through the intermediate vertex exists. + if (distanceMatrix[source][intermediate] + distanceMatrix[intermediate][destination] < distanceMatrix[source][destination]) { distanceMatrix[source][destination] = distanceMatrix[source][intermediate] + distanceMatrix[intermediate][destination]; } } } } + + printDistanceMatrix(); + } + + /** + * Prints the distance matrix representing the shortest paths between all pairs of vertices. + * The rows and columns correspond to the source and destination vertices. + */ + private void printDistanceMatrix() { + // Print header for vertices for (int source = 1; source <= numberofvertices; source++) { System.out.print("\t" + source); } System.out.println(); + + // Print the distance matrix for (int source = 1; source <= numberofvertices; source++) { System.out.print(source + "\t"); for (int destination = 1; destination <= numberofvertices; destination++) { @@ -46,27 +79,7 @@ public void floydwarshall(int[][] adjacencyMatrix) { // calculates all the dista } } - public static void main(String... arg) { - Scanner scan = new Scanner(System.in); - System.out.println("Enter the number of vertices"); - int numberOfVertices = scan.nextInt(); - int[][] adjacencyMatrix = new int[numberOfVertices + 1][numberOfVertices + 1]; - System.out.println("Enter the Weighted Matrix for the graph"); - for (int source = 1; source <= numberOfVertices; source++) { - for (int destination = 1; destination <= numberOfVertices; destination++) { - adjacencyMatrix[source][destination] = scan.nextInt(); - if (source == destination) { - adjacencyMatrix[source][destination] = 0; - continue; - } - if (adjacencyMatrix[source][destination] == 0) { - adjacencyMatrix[source][destination] = INFINITY; - } - } - } - System.out.println("The Transitive Closure of the Graph"); - FloydWarshall floydwarshall = new FloydWarshall(numberOfVertices); - floydwarshall.floydwarshall(adjacencyMatrix); - scan.close(); + public Object[] getDistanceMatrix() { + return distanceMatrix; } } diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java b/src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java index af2665cfaebb..b3a2053d54b5 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java @@ -3,12 +3,30 @@ import java.util.LinkedList; import java.util.Queue; +/** + * This class implements the Ford-Fulkerson algorithm to compute the maximum flow + * in a flow network. + * + *

The algorithm uses breadth-first search (BFS) to find augmenting paths from + * the source vertex to the sink vertex, updating the flow in the network until + * no more augmenting paths can be found.

+ */ public final class FordFulkerson { private static final int INF = Integer.MAX_VALUE; private FordFulkerson() { } + /** + * Computes the maximum flow in a flow network using the Ford-Fulkerson algorithm. + * + * @param vertexCount the number of vertices in the flow network + * @param capacity a 2D array representing the capacity of edges in the network + * @param flow a 2D array representing the current flow in the network + * @param source the source vertex in the flow network + * @param sink the sink vertex in the flow network + * @return the total maximum flow from the source to the sink + */ public static int networkFlow(int vertexCount, int[][] capacity, int[][] flow, int source, int sink) { int totalFlow = 0; diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java b/src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java index f12e3892b1b2..5c95850c4971 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java @@ -1,8 +1,14 @@ package com.thealgorithms.datastructures.graphs; +import java.util.Arrays; + /** - * Java program for Hamiltonian Cycle - * wikipedia + * Java program to find a Hamiltonian Cycle in a graph. + * A Hamiltonian Cycle is a cycle that visits every vertex exactly once + * and returns to the starting vertex. + * + *

For more details, see the + * Wikipedia article. * * @author Akshay Dubey */ @@ -14,30 +20,30 @@ public class HamiltonianCycle { private int[][] graph; /** - * Find hamiltonian cycle for given graph G(V,E) + * Finds a Hamiltonian Cycle for the given graph. * - * @param graph Adjacency matrix of a graph G(V, E) - * for which hamiltonian path is to be found - * @return Array containing hamiltonian cycle - * else returns 1D array with value -1. + * @param graph Adjacency matrix representing the graph G(V, E), where V is + * the set of vertices and E is the set of edges. + * @return An array representing the Hamiltonian cycle if found, otherwise an + * array filled with -1 indicating no Hamiltonian cycle exists. */ public int[] findHamiltonianCycle(int[][] graph) { + // Single vertex graph + if (graph.length == 1) { + return new int[] {0, 0}; + } + this.vertex = graph.length; this.cycle = new int[this.vertex + 1]; - // Initialize path array with -1 value - for (int i = 0; i < this.cycle.length; i++) { - this.cycle[i] = -1; - } + // Initialize the cycle array with -1 to represent unvisited vertices + Arrays.fill(this.cycle, -1); this.graph = graph; - this.cycle[0] = 0; this.pathCount = 1; if (!isPathFound(0)) { - for (int i = 0; i < this.cycle.length; i++) { - this.cycle[i] = -1; - } + Arrays.fill(this.cycle, -1); } else { this.cycle[this.cycle.length - 1] = this.cycle[0]; } @@ -46,11 +52,10 @@ public int[] findHamiltonianCycle(int[][] graph) { } /** - * function to find paths recursively - * Find paths recursively from given vertex + * Recursively searches for a Hamiltonian cycle from the given vertex. * - * @param vertex Vertex from which path is to be found - * @returns true if path is found false otherwise + * @param vertex The current vertex from which to explore paths. + * @return {@code true} if a Hamiltonian cycle is found, otherwise {@code false}. */ public boolean isPathFound(int vertex) { boolean isLastVertexConnectedToStart = this.graph[vertex][0] == 1 && this.pathCount == this.vertex; @@ -58,31 +63,26 @@ public boolean isPathFound(int vertex) { return true; } - /* all vertices selected but last vertex not linked to 0 **/ + // If all vertices are visited but the last vertex is not connected to the start if (this.pathCount == this.vertex) { return false; } for (int v = 0; v < this.vertex; v++) { - /* if connected **/ - if (this.graph[vertex][v] == 1) { - /* add to path **/ - this.cycle[this.pathCount++] = v; - - /* remove connection **/ + if (this.graph[vertex][v] == 1) { // Check if there is an edge + this.cycle[this.pathCount++] = v; // Add the vertex to the cycle this.graph[vertex][v] = 0; this.graph[v][vertex] = 0; - /* if vertex not already selected solve recursively **/ + // Recursively attempt to complete the cycle if (!isPresent(v)) { return isPathFound(v); } - /* restore connection **/ + // Restore the edge if the path does not work this.graph[vertex][v] = 1; this.graph[v][vertex] = 1; - /* remove path **/ this.cycle[--this.pathCount] = -1; } } @@ -90,10 +90,10 @@ public boolean isPathFound(int vertex) { } /** - * function to check if path is already selected - * Check if path is already selected + * Checks if a vertex is already part of the current Hamiltonian path. * - * @param vertex Starting vertex + * @param vertex The vertex to check. + * @return {@code true} if the vertex is already in the path, otherwise {@code false}. */ public boolean isPresent(int vertex) { for (int i = 0; i < pathCount - 1; i++) { @@ -101,7 +101,6 @@ public boolean isPresent(int vertex) { return true; } } - return false; } } diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithm.java new file mode 100644 index 000000000000..351bd5b009e8 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithm.java @@ -0,0 +1,200 @@ +package com.thealgorithms.datastructures.graphs; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * This class implements Johnson's algorithm for finding all-pairs shortest paths in a weighted, + * directed graph that may contain negative edge weights. + * + * Johnson's algorithm works by using the Bellman-Ford algorithm to compute a transformation of the + * input graph that removes all negative weights, allowing Dijkstra's algorithm to be used for + * efficient shortest path computations. + * + * Time Complexity: O(V^2 * log(V) + V*E) + * Space Complexity: O(V^2) + * + * Where V is the number of vertices and E is the number of edges in the graph. + * + * For more information, please visit {@link https://en.wikipedia.org/wiki/Johnson%27s_algorithm} + */ +public final class JohnsonsAlgorithm { + + private static final double INF = Double.POSITIVE_INFINITY; + + private JohnsonsAlgorithm() { + } + + /** + * Executes Johnson's algorithm on the given graph. + * Steps: + * 1. Add a new vertex to the graph and run Bellman-Ford to compute modified weights + * 2. t the graph using the modified weights + * 3. Run Dijkstra's algorithm for each vertex to compute the shortest paths + * The final result is a 2D array of shortest distances between all pairs of vertices. + * + * @param graph The input graph represented as an adjacency matrix. + * @return A 2D array representing the shortest distances between all pairs of vertices. + */ + public static double[][] johnsonAlgorithm(double[][] graph) { + int numVertices = graph.length; + double[][] edges = convertToEdgeList(graph); + + double[] modifiedWeights = bellmanFord(edges, numVertices); + + double[][] reweightedGraph = reweightGraph(graph, modifiedWeights); + + double[][] shortestDistances = new double[numVertices][numVertices]; + for (int source = 0; source < numVertices; source++) { + shortestDistances[source] = dijkstra(reweightedGraph, source, modifiedWeights); + } + + return shortestDistances; + } + + /** + * Converts the adjacency matrix representation of the graph to an edge list. + * + * @param graph The input graph as an adjacency matrix. + * @return An array of edges, where each edge is represented as [from, to, weight]. + */ + public static double[][] convertToEdgeList(double[][] graph) { + int numVertices = graph.length; + List edgeList = new ArrayList<>(); + + for (int i = 0; i < numVertices; i++) { + for (int j = 0; j < numVertices; j++) { + if (i != j && !Double.isInfinite(graph[i][j])) { + // Only add edges that are not self-loops and have a finite weight + edgeList.add(new double[] {i, j, graph[i][j]}); + } + } + } + + return edgeList.toArray(new double[0][]); + } + + /** + * Implements the Bellman-Ford algorithm to compute the shortest paths from a new vertex + * to all other vertices. This is used to calculate the weight function h(v) for reweighting. + * + * @param edges The edge list of the graph. + * @param numVertices The number of vertices in the original graph. + * @return An array of modified weights for each vertex. + */ + private static double[] bellmanFord(double[][] edges, int numVertices) { + double[] dist = new double[numVertices + 1]; + Arrays.fill(dist, INF); + dist[numVertices] = 0; + + // Add edges from the new vertex to all original vertices + double[][] allEdges = Arrays.copyOf(edges, edges.length + numVertices); + for (int i = 0; i < numVertices; i++) { + allEdges[edges.length + i] = new double[] {numVertices, i, 0}; + } + + // Relax all edges V times + for (int i = 0; i < numVertices; i++) { + for (double[] edge : allEdges) { + int u = (int) edge[0]; + int v = (int) edge[1]; + double weight = edge[2]; + if (dist[u] != INF && dist[u] + weight < dist[v]) { + dist[v] = dist[u] + weight; + } + } + } + + // Check for negative weight cycles + for (double[] edge : allEdges) { + int u = (int) edge[0]; + int v = (int) edge[1]; + double weight = edge[2]; + if (dist[u] + weight < dist[v]) { + throw new IllegalArgumentException("Graph contains a negative weight cycle"); + } + } + + return Arrays.copyOf(dist, numVertices); + } + + /** + * Reweights the graph using the modified weights computed by Bellman-Ford. + * + * @param graph The original graph. + * @param modifiedWeights The modified weights from Bellman-Ford. + * @return The reweighted graph. + */ + public static double[][] reweightGraph(double[][] graph, double[] modifiedWeights) { + int numVertices = graph.length; + double[][] reweightedGraph = new double[numVertices][numVertices]; + + for (int i = 0; i < numVertices; i++) { + for (int j = 0; j < numVertices; j++) { + if (graph[i][j] != 0) { + // New weight = original weight + h(u) - h(v) + reweightedGraph[i][j] = graph[i][j] + modifiedWeights[i] - modifiedWeights[j]; + } + } + } + + return reweightedGraph; + } + + /** + * Implements Dijkstra's algorithm for finding shortest paths from a source vertex. + * + * @param reweightedGraph The reweighted graph to run Dijkstra's on. + * @param source The source vertex. + * @param modifiedWeights The modified weights from Bellman-Ford. + * @return An array of shortest distances from the source to all other vertices. + */ + public static double[] dijkstra(double[][] reweightedGraph, int source, double[] modifiedWeights) { + int numVertices = reweightedGraph.length; + double[] dist = new double[numVertices]; + boolean[] visited = new boolean[numVertices]; + Arrays.fill(dist, INF); + dist[source] = 0; + + for (int count = 0; count < numVertices - 1; count++) { + int u = minDistance(dist, visited); + visited[u] = true; + + for (int v = 0; v < numVertices; v++) { + if (!visited[v] && reweightedGraph[u][v] != 0 && dist[u] != INF && dist[u] + reweightedGraph[u][v] < dist[v]) { + dist[v] = dist[u] + reweightedGraph[u][v]; + } + } + } + + // Adjust distances back to the original graph weights + for (int i = 0; i < numVertices; i++) { + if (dist[i] != INF) { + dist[i] = dist[i] - modifiedWeights[source] + modifiedWeights[i]; + } + } + + return dist; + } + + /** + * Finds the vertex with the minimum distance value from the set of vertices + * not yet included in the shortest path tree. + * + * @param dist Array of distances. + * @param visited Array of visited vertices. + * @return The index of the vertex with minimum distance. + */ + public static int minDistance(double[] dist, boolean[] visited) { + double min = INF; + int minIndex = -1; + for (int v = 0; v < dist.length; v++) { + if (!visited[v] && dist[v] <= min) { + min = dist[v]; + minIndex = v; + } + } + return minIndex; + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java index d5035cf625a6..9a97bc3f4808 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java @@ -9,96 +9,120 @@ import java.util.Set; /** - * A class that represents the adjaceny list of a graph + * A class representing the adjacency list of a directed graph. The adjacency list + * maintains a mapping of vertices to their adjacent vertices. + * + * @param the type of vertices, extending Comparable to ensure that vertices + * can be compared */ class AdjacencyList> { Map> adj; + /** + * Constructor to initialize the adjacency list. + */ AdjacencyList() { - adj = new LinkedHashMap>(); + adj = new LinkedHashMap<>(); } /** - * This function adds an Edge to the adjaceny list + * Adds a directed edge from one vertex to another in the adjacency list. + * If the vertex does not exist, it will be added to the list. * - * @param from , the vertex the edge is from - * @param to, the vertex the edge is going to + * @param from the starting vertex of the directed edge + * @param to the destination vertex of the directed edge */ void addEdge(E from, E to) { - try { - adj.get(from).add(to); - } catch (Exception E) { - adj.put(from, new ArrayList()); - adj.get(from).add(to); + if (!adj.containsKey(from)) { + adj.put(from, new ArrayList<>()); } + adj.get(from).add(to); if (!adj.containsKey(to)) { - adj.put(to, new ArrayList()); + adj.put(to, new ArrayList<>()); } } /** - * @param v, A vertex in a graph - * @return returns an ArrayList of all the adjacents of vertex v + * Retrieves the list of adjacent vertices for a given vertex. + * + * @param v the vertex whose adjacent vertices are to be fetched + * @return an ArrayList of adjacent vertices for vertex v */ ArrayList getAdjacents(E v) { return adj.get(v); } /** - * @return returns a set of all vertices in the graph + * Retrieves the set of all vertices present in the graph. + * + * @return a set containing all vertices in the graph */ Set getVertices() { return adj.keySet(); } } +/** + * A class that performs topological sorting on a directed graph using Kahn's algorithm. + * + * @param the type of vertices, extending Comparable to ensure that vertices + * can be compared + */ class TopologicalSort> { AdjacencyList graph; Map inDegree; + /** + * Constructor to initialize the topological sorting class with a given graph. + * + * @param graph the directed graph represented as an adjacency list + */ TopologicalSort(AdjacencyList graph) { this.graph = graph; } /** - * Calculates the in degree of all vertices + * Calculates the in-degree of all vertices in the graph. The in-degree is + * the number of edges directed into a vertex. */ void calculateInDegree() { inDegree = new HashMap<>(); for (E vertex : graph.getVertices()) { - if (!inDegree.containsKey(vertex)) { - inDegree.put(vertex, 0); - } + inDegree.putIfAbsent(vertex, 0); for (E adjacent : graph.getAdjacents(vertex)) { - try { - inDegree.put(adjacent, inDegree.get(adjacent) + 1); - } catch (Exception e) { - inDegree.put(adjacent, 1); - } + inDegree.put(adjacent, inDegree.getOrDefault(adjacent, 0) + 1); } } } /** - * Returns an ArrayList with vertices arranged in topological order + * Returns an ArrayList containing the vertices of the graph arranged in + * topological order. Topological sorting ensures that for any directed edge + * (u, v), vertex u appears before vertex v in the ordering. + * + * @return an ArrayList of vertices in topological order + * @throws IllegalStateException if the graph contains a cycle */ ArrayList topSortOrder() { calculateInDegree(); - Queue q = new LinkedList(); + Queue q = new LinkedList<>(); - for (final var entry : inDegree.entrySet()) { + for (var entry : inDegree.entrySet()) { if (entry.getValue() == 0) { q.add(entry.getKey()); } } ArrayList answer = new ArrayList<>(); + int processedVertices = 0; while (!q.isEmpty()) { E current = q.poll(); answer.add(current); + processedVertices++; + for (E adjacent : graph.getAdjacents(current)) { inDegree.put(adjacent, inDegree.get(adjacent) - 1); if (inDegree.get(adjacent) == 0) { @@ -107,12 +131,16 @@ ArrayList topSortOrder() { } } + if (processedVertices != graph.getVertices().size()) { + throw new IllegalStateException("Graph contains a cycle, topological sort not possible"); + } + return answer; } } /** - * A driver class that sorts a given graph in topological order. + * A driver class that sorts a given graph in topological order using Kahn's algorithm. */ public final class KahnsAlgorithm { private KahnsAlgorithm() { @@ -130,7 +158,7 @@ public static void main(String[] args) { TopologicalSort topSort = new TopologicalSort<>(graph); - // Printing the order + // Printing the topological order for (String s : topSort.topSortOrder()) { System.out.print(s + " "); } diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java b/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java index c5f15839f997..78a184f042b5 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java @@ -5,82 +5,91 @@ import java.util.Stack; /** - * Java program that implements Kosaraju Algorithm. - * @author Shivanagouda S A + * This class implements the Kosaraju Algorithm to find all the Strongly Connected Components (SCCs) + * of a directed graph. Kosaraju's algorithm runs in linear time and leverages the concept that + * the SCCs of a directed graph remain the same in its transpose (reverse) graph. + * *

- * Kosaraju algorithm is a linear time algorithm to find the strongly connected components of a -directed graph, which, from here onwards will be referred by SCC. It leverages the fact that the -transpose graph (same graph with all the edges reversed) has exactly the same SCCs as the original -graph. - - * A graph is said to be strongly connected if every vertex is reachable from every other vertex. -The SCCs of a directed graph form a partition into subgraphs that are themselves strongly -connected. Single node is always a SCC. - - * Example: - -0 <--- 2 -------> 3 -------- > 4 ---- > 7 -| ^ | ^ ^ -| / | \ / -| / | \ / -v / v \ / -1 5 --> 6 - -For the above graph, the SCC list goes as follows: -0, 1, 2 -3 -4, 5, 6 -7 - -We can also see that order of the nodes in an SCC doesn't matter since they are in cycle. - -{@summary} - * Kosaraju Algorithm: -1. Perform DFS traversal of the graph. Push node to stack before returning. This gives edges -sorted by lowest finish time. -2. Find the transpose graph by reversing the edges. -3. Pop nodes one by one from the stack and again to DFS on the modified graph. - -The transpose graph of the above graph: -0 ---> 2 <------- 3 <------- 4 <------ 7 -^ / ^ \ / -| / | \ / -| / | \ / -| v | v v -1 5 <--- 6 - -We can observe that this graph has the same SCC as that of original graph. - + * A strongly connected component (SCC) of a directed graph is a subgraph where every vertex + * is reachable from every other vertex in the subgraph. The Kosaraju algorithm is particularly + * efficient for finding SCCs because it performs two Depth First Search (DFS) passes on the + * graph and its transpose. + *

+ * + *

Algorithm:

+ *
    + *
  1. Perform DFS on the original graph and push nodes to a stack in the order of their finishing time.
  2. + *
  3. Generate the transpose (reversed edges) of the original graph.
  4. + *
  5. Perform DFS on the transpose graph, using the stack from the first DFS. Each DFS run on the transpose graph gives a SCC.
  6. + *
+ * + *

Example Graph:

+ *
+ * 0 <--- 2 -------> 3 -------- > 4 ---- > 7
+ * |     ^                      | ^       ^
+ * |    /                       |  \     /
+ * |   /                        |   \   /
+ * v  /                         v    \ /
+ * 1                            5 --> 6
+ * 
+ * + *

SCCs in the example:

+ *
    + *
  • {0, 1, 2}
  • + *
  • {3}
  • + *
  • {4, 5, 6}
  • + *
  • {7}
  • + *
+ * + *

The order of nodes in an SCC does not matter because every node in an SCC is reachable from every other node within the same SCC.

+ * + *

Graph Transpose Example:

+ *
+ * 0 ---> 2 <------- 3 <------- 4 <------ 7
+ * ^     /                      ^ \       /
+ * |    /                       |  \     /
+ * |   /                        |   \   /
+ * |  v                         |    v v
+ * 1                            5 <--- 6
+ * 
+ * + * The SCCs of this transpose graph are the same as the original graph. */ - public class Kosaraju { - // Sort edges according to lowest finish time - Stack stack = new Stack(); + // Stack to sort edges by the lowest finish time (used in the first DFS) + private final Stack stack = new Stack<>(); - // Store each component + // Store each strongly connected component private List scc = new ArrayList<>(); - // All the strongly connected components - private List> sccsList = new ArrayList<>(); + // List of all SCCs + private final List> sccsList = new ArrayList<>(); /** + * Main function to perform Kosaraju's Algorithm. + * Steps: + * 1. Sort nodes by the lowest finishing time + * 2. Create the transpose (reverse edges) of the original graph + * 3. Find SCCs by performing DFS on the transpose graph + * 4. Return the list of SCCs * - * @param v Node count - * @param list Adjacency list of graph - * @return List of SCCs + * @param v the number of vertices in the graph + * @param list the adjacency list representing the directed graph + * @return a list of SCCs where each SCC is a list of vertices */ public List> kosaraju(int v, List> list) { - sortEdgesByLowestFinishTime(v, list); - List> transposeGraph = createTransposeMatrix(v, list); - findStronglyConnectedComponents(v, transposeGraph); - return sccsList; } + /** + * Performs DFS on the original graph to sort nodes by their finishing times. + * @param v the number of vertices in the graph + * @param list the adjacency list representing the original graph + */ private void sortEdgesByLowestFinishTime(int v, List> list) { int[] vis = new int[v]; for (int i = 0; i < v; i++) { @@ -90,8 +99,14 @@ private void sortEdgesByLowestFinishTime(int v, List> list) { } } + /** + * Creates the transpose (reverse) of the original graph. + * @param v the number of vertices in the graph + * @param list the adjacency list representing the original graph + * @return the adjacency list representing the transposed graph + */ private List> createTransposeMatrix(int v, List> list) { - var transposeGraph = new ArrayList>(v); + List> transposeGraph = new ArrayList<>(v); for (int i = 0; i < v; i++) { transposeGraph.add(new ArrayList<>()); } @@ -104,14 +119,14 @@ private List> createTransposeMatrix(int v, List> lis } /** - * - * @param v Node count - * @param transposeGraph Transpose of the given adjacency list + * Finds the strongly connected components (SCCs) by performing DFS on the transposed graph. + * @param v the number of vertices in the graph + * @param transposeGraph the adjacency list representing the transposed graph */ public void findStronglyConnectedComponents(int v, List> transposeGraph) { int[] vis = new int[v]; while (!stack.isEmpty()) { - var node = stack.pop(); + int node = stack.pop(); if (vis[node] == 0) { dfs2(node, vis, transposeGraph); sccsList.add(scc); @@ -120,7 +135,12 @@ public void findStronglyConnectedComponents(int v, List> transpose } } - // Dfs to store the nodes in order of lowest finish time + /** + * Performs DFS on the original graph and pushes nodes onto the stack in order of their finish time. + * @param node the current node being visited + * @param vis array to keep track of visited nodes + * @param list the adjacency list of the graph + */ private void dfs(int node, int[] vis, List> list) { vis[node] = 1; for (Integer neighbour : list.get(node)) { @@ -131,7 +151,12 @@ private void dfs(int node, int[] vis, List> list) { stack.push(node); } - // Dfs to find all the nodes of each strongly connected component + /** + * Performs DFS on the transposed graph to find the strongly connected components. + * @param node the current node being visited + * @param vis array to keep track of visited nodes + * @param list the adjacency list of the transposed graph + */ private void dfs2(int node, int[] vis, List> list) { vis[node] = 1; for (Integer neighbour : list.get(node)) { diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java b/src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java index eb5b65d5c0d4..25c4548daa7a 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java @@ -1,27 +1,34 @@ package com.thealgorithms.datastructures.graphs; -// Problem -> Connect all the edges with the minimum cost. -// Possible Solution -> Kruskal Algorithm (KA), KA finds the minimum-spanning-tree, which means, the -// group of edges with the minimum sum of their weights that connect the whole graph. -// The graph needs to be connected, because if there are nodes impossible to reach, there are no -// edges that could connect every node in the graph. -// KA is a Greedy Algorithm, because edges are analysed based on their weights, that is why a -// Priority Queue is used, to take first those less weighted. -// This implementations below has some changes compared to conventional ones, but they are explained -// all along the code. import java.util.Comparator; import java.util.HashSet; import java.util.PriorityQueue; +/** + * The Kruskal class implements Kruskal's Algorithm to find the Minimum Spanning Tree (MST) + * of a connected, undirected graph. The algorithm constructs the MST by selecting edges + * with the least weight, ensuring no cycles are formed, and using union-find to track the + * connected components. + * + *

Key Features:

+ *
    + *
  • The graph is represented using an adjacency list, where each node points to a set of edges.
  • + *
  • Each edge is processed in ascending order of weight using a priority queue.
  • + *
  • The algorithm stops when all nodes are connected or no more edges are available.
  • + *
+ * + *

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

+ */ public class Kruskal { - // Complexity: O(E log V) time, where E is the number of edges in the graph and V is the number - // of vertices - private static class Edge { + /** + * Represents an edge in the graph with a source, destination, and weight. + */ + static class Edge { - private int from; - private int to; - private int weight; + int from; + int to; + int weight; Edge(int from, int to, int weight) { this.from = from; @@ -30,51 +37,30 @@ private static class Edge { } } - private static void addEdge(HashSet[] graph, int from, int to, int weight) { + /** + * Adds an edge to the graph. + * + * @param graph the adjacency list representing the graph + * @param from the source vertex of the edge + * @param to the destination vertex of the edge + * @param weight the weight of the edge + */ + static void addEdge(HashSet[] graph, int from, int to, int weight) { graph[from].add(new Edge(from, to, weight)); } - public static void main(String[] args) { - HashSet[] graph = new HashSet[7]; - for (int i = 0; i < graph.length; i++) { - graph[i] = new HashSet<>(); - } - addEdge(graph, 0, 1, 2); - addEdge(graph, 0, 2, 3); - addEdge(graph, 0, 3, 3); - addEdge(graph, 1, 2, 4); - addEdge(graph, 2, 3, 5); - addEdge(graph, 1, 4, 3); - addEdge(graph, 2, 4, 1); - addEdge(graph, 3, 5, 7); - addEdge(graph, 4, 5, 8); - addEdge(graph, 5, 6, 9); - - System.out.println("Initial Graph: "); - for (int i = 0; i < graph.length; i++) { - for (Edge edge : graph[i]) { - System.out.println(i + " <-- weight " + edge.weight + " --> " + edge.to); - } - } - - Kruskal k = new Kruskal(); - HashSet[] solGraph = k.kruskal(graph); - - System.out.println("\nMinimal Graph: "); - for (int i = 0; i < solGraph.length; i++) { - for (Edge edge : solGraph[i]) { - System.out.println(i + " <-- weight " + edge.weight + " --> " + edge.to); - } - } - } - + /** + * Kruskal's algorithm to find the Minimum Spanning Tree (MST) of a graph. + * + * @param graph the adjacency list representing the input graph + * @return the adjacency list representing the MST + */ public HashSet[] kruskal(HashSet[] graph) { int nodes = graph.length; - int[] captain = new int[nodes]; - // captain of i, stores the set with all the connected nodes to i + int[] captain = new int[nodes]; // Stores the "leader" of each node's connected component HashSet[] connectedGroups = new HashSet[nodes]; HashSet[] minGraph = new HashSet[nodes]; - PriorityQueue edges = new PriorityQueue<>((Comparator.comparingInt(edge -> edge.weight))); + PriorityQueue edges = new PriorityQueue<>(Comparator.comparingInt(edge -> edge.weight)); for (int i = 0; i < nodes; i++) { minGraph[i] = new HashSet<>(); connectedGroups[i] = new HashSet<>(); @@ -83,18 +69,21 @@ public HashSet[] kruskal(HashSet[] graph) { edges.addAll(graph[i]); } int connectedElements = 0; - // as soon as two sets merge all the elements, the algorithm must stop while (connectedElements != nodes && !edges.isEmpty()) { Edge edge = edges.poll(); - // This if avoids cycles + + // Avoid forming cycles by checking if the nodes belong to different connected components if (!connectedGroups[captain[edge.from]].contains(edge.to) && !connectedGroups[captain[edge.to]].contains(edge.from)) { - // merge sets of the captains of each point connected by the edge + // Merge the two sets of nodes connected by the edge connectedGroups[captain[edge.from]].addAll(connectedGroups[captain[edge.to]]); - // update captains of the elements merged + + // Update the captain for each merged node connectedGroups[captain[edge.from]].forEach(i -> captain[i] = captain[edge.from]); - // add Edge to minimal graph + + // Add the edge to the resulting MST graph addEdge(minGraph, edge.from, edge.to, edge.weight); - // count how many elements have been merged + + // Update the count of connected nodes connectedElements = connectedGroups[captain[edge.from]].size(); } } diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/MatrixGraphs.java b/src/main/java/com/thealgorithms/datastructures/graphs/MatrixGraphs.java index 902553f9a54c..c1d47df457da 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/MatrixGraphs.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/MatrixGraphs.java @@ -102,7 +102,7 @@ public int numberOfVertices() { /** * Updates the number of edges in the graph * - * @param newNumberOfEdges + * @param newNumberOfEdges the new number of edges * */ private void setNumberOfEdges(int newNumberOfEdges) { @@ -202,7 +202,7 @@ public boolean addEdge(int from, int to) { * exists and is removed */ public boolean removeEdge(int from, int to) { - if (!this.vertexDoesExist(from) || !this.vertexDoesExist(to)) { + if (this.vertexDoesExist(from) && this.vertexDoesExist(to)) { if (this.adjacencyOfEdgeDoesExist(from, to)) { this.adjacency()[from][to] = AdjacencyMatrixGraph.EDGE_NONE; this.adjacency()[to][from] = AdjacencyMatrixGraph.EDGE_NONE; @@ -223,14 +223,14 @@ public boolean removeEdge(int from, int to) { public List depthFirstOrder(int startVertex) { // If the startVertex is invalid, return an empty list if (startVertex >= vertexCount || startVertex < 0) { - return new ArrayList(); + return new ArrayList<>(); } // Create an array to track the visited vertices boolean[] visited = new boolean[vertexCount]; // Create a list to keep track of the order of our traversal - ArrayList orderList = new ArrayList(); + ArrayList orderList = new ArrayList<>(); // Perform our DFS algorithm depthFirstOrder(startVertex, visited, orderList); @@ -278,18 +278,18 @@ private void depthFirstOrder(int currentVertex, boolean[] visited, List public List breadthFirstOrder(int startVertex) { // If the specified startVertex is invalid, return an empty list if (startVertex >= vertexCount || startVertex < 0) { - return new ArrayList(); + return new ArrayList<>(); } // Create an array to keep track of the visited vertices boolean[] visited = new boolean[vertexCount]; // Create a list to keep track of the ordered vertices - ArrayList orderList = new ArrayList(); + ArrayList orderList = new ArrayList<>(); // Create a queue for our BFS algorithm and add the startVertex // to the queue - Queue queue = new LinkedList(); + Queue queue = new LinkedList<>(); queue.add(startVertex); // Continue until the queue is empty @@ -327,19 +327,19 @@ public List breadthFirstOrder(int startVertex) { * @return returns a string describing this graph */ public String toString() { - String s = " "; + StringBuilder s = new StringBuilder(" "); for (int i = 0; i < this.numberOfVertices(); i++) { - s = s + i + " "; + s.append(i).append(" "); } - s = s + " \n"; + s.append(" \n"); for (int i = 0; i < this.numberOfVertices(); i++) { - s = s + i + " : "; + s.append(i).append(" : "); for (int j = 0; j < this.numberOfVertices(); j++) { - s = s + this.adjMatrix[i][j] + " "; + s.append(this.adjMatrix[i][j]).append(" "); } - s = s + "\n"; + s.append("\n"); } - return s; + return s.toString(); } } diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/PrimMST.java b/src/main/java/com/thealgorithms/datastructures/graphs/PrimMST.java index 7e11862786f6..8017c18ce6ac 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/PrimMST.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/PrimMST.java @@ -1,19 +1,17 @@ package com.thealgorithms.datastructures.graphs; /** - * A Java program for Prim's Minimum Spanning Tree (MST) algorithm. adjacency - * matrix representation of the graph + * A Java program for Prim's Minimum Spanning Tree (MST) algorithm. + * Adjacency matrix representation of the graph. */ -class PrimMST { +public class PrimMST { // Number of vertices in the graph - private static final int V = 5; - // A utility function to find the vertex with minimum key - // value, from the set of vertices not yet included in MST + // A utility function to find the vertex with the minimum key + // value, from the set of vertices not yet included in the MST int minKey(int[] key, Boolean[] mstSet) { - // Initialize min value int min = Integer.MAX_VALUE; int minIndex = -1; @@ -27,54 +25,30 @@ int minKey(int[] key, Boolean[] mstSet) { return minIndex; } - // A utility function to print the constructed MST stored in - // parent[] - void printMST(int[] parent, int n, int[][] graph) { - System.out.println("Edge Weight"); - for (int i = 1; i < V; i++) { - System.out.println(parent[i] + " - " + i + " " + graph[i][parent[i]]); - } - } - - // Function to construct and print MST for a graph represented - // using adjacency matrix representation - void primMST(int[][] graph) { - // Array to store constructed MST - int[] parent = new int[V]; - - // Key values used to pick minimum weight edge in cut - int[] key = new int[V]; + // Function to construct MST for a graph using adjacency matrix representation + public int[] primMST(int[][] graph) { + int[] parent = new int[V]; // Array to store constructed MST + int[] key = new int[V]; // Key values to pick minimum weight edge + Boolean[] mstSet = new Boolean[V]; // Vertices not yet included in MST - // To represent set of vertices not yet included in MST - Boolean[] mstSet = new Boolean[V]; - - // Initialize all keys as INFINITE + // Initialize all keys as INFINITE and mstSet[] as false for (int i = 0; i < V; i++) { key[i] = Integer.MAX_VALUE; mstSet[i] = Boolean.FALSE; } - // Always include first 1st vertex in MST. - key[0] = 0; // Make key 0 so that this vertex is - // picked as first vertex + // Always include the first vertex in MST + key[0] = 0; // Make key 0 to pick the first vertex parent[0] = -1; // First node is always root of MST // The MST will have V vertices for (int count = 0; count < V - 1; count++) { - // Pick thd minimum key vertex from the set of vertices - // not yet included in MST + // Pick the minimum key vertex not yet included in MST int u = minKey(key, mstSet); - - // Add the picked vertex to the MST Set mstSet[u] = Boolean.TRUE; - // Update key value and parent index of the adjacent - // vertices of the picked vertex. Consider only those - // vertices which are not yet included in MST - for (int v = 0; v < V; v++) // Update the key only if graph[u][v] is smaller than key[v] // mstSet[v] is - // false for vertices not yet included in MST // graph[u][v] is non zero only - // for adjacent vertices of m - { + // Update key value and parent index of adjacent vertices of the picked vertex + for (int v = 0; v < V; v++) { if (graph[u][v] != 0 && !mstSet[v] && graph[u][v] < key[v]) { parent[v] = u; key[v] = graph[u][v]; @@ -82,29 +56,6 @@ void primMST(int[][] graph) { } } - // print the constructed MST - printMST(parent, V, graph); - } - - public static void main(String[] args) { - /* Let us create the following graph - 2 3 - (0)--(1)--(2) - | / \ | - 6| 8/ \5 |7 - | / \ | - (3)-------(4) - 9 */ - PrimMST t = new PrimMST(); - int[][] graph = new int[][] { - {0, 2, 0, 6, 0}, - {2, 0, 3, 8, 5}, - {0, 3, 0, 0, 7}, - {6, 8, 0, 0, 9}, - {0, 5, 7, 9, 0}, - }; - - // Print the solution - t.primMST(graph); + return parent; // Return the MST parent array } } diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/README.md b/src/main/java/com/thealgorithms/datastructures/graphs/README.md index 057adb46acf5..4798e372667b 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/README.md +++ b/src/main/java/com/thealgorithms/datastructures/graphs/README.md @@ -88,4 +88,39 @@ It means there are edges from 0 to 1, 2, and 3; from 1 to 0 and 2, and so on. 2 1 1 0 0 0 3 1 0 0 0 1 4 0 0 0 1 0 -``` + +###Graph Terminologies + +Degree of a vertex: Number of edges that are incident at a vertex. +Weighted graph: A graph that has weights assigned for each of the edges (used in cases such as shortest path problems). +Connected components: A set of vertices that can reach others from it but not to those outside this connected component. +Cycle: A path that begins and ends at the same vertex. +Bipartite Graph: A graph whose vertices can be partitioned into two disjoint sets, with every edge connecting a vertex in one set to a vertex in the other set. + +###Graph Algorithms + +Breadth-First Search: It explores neighbors in layer after layer and applies on shortest path problems for unweighted graphs. +Depth-First Search (DFS): It continues moving up as far along each branch as possible before backtracking. DFS is typically used for traversing all nodes and testing connectivity. +Dijkstra's Algorithm: This algorithm finds the shortest path from a single starting vertex to all other vertices in a weighted graph. +Prim's and Kruskal's Algorithm: To find the minimum spanning tree. +Bellman-Ford Algorithm: This algorithm solves shortest path problems even when there are negative weights. +Graph Types +Multigraphs: Graphs with more edges between the same set of vertices. +Complete Graphs: A graph in which there is a unique edge between each pair of vertices. +Planar Graphs: A graph that can be drawn in a plane such that no two edges cross. + +###Graph Algorithm Applications + +Google Maps (Dijkstra's Algorithm): How maps apps find shortest routes. +Job Scheduling: Topological Sort A real application of DAG (Directed Acyclic Graph) to manage the dependency of jobs between tasks. +Web Crawling: How to use BFS for web crawlers to index pages in search engines. +Big-O Complexity of Graph Operations +Adjacency List vs Adjacency Matrix : Provide comparison tables of time complexity for operations such as addition of an edge, checking if an edge exists, etc. +BFS and DFS Complexity : Describe their computational cost + +###Common Graph Problems + +Graph Coloring +Finding Bridges and Articulation Points +Finding Strongly Connected Components +Maximum Flow (Ford-Fulkerson algorithm) diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithm.java index de50044256c6..91974ba13319 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithm.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithm.java @@ -5,66 +5,73 @@ import java.util.Stack; /** - * Java program that implements Tarjan's Algorithm. - * @author Shivanagouda S A + * Java program that implements Tarjan's Algorithm to find Strongly Connected Components (SCCs) in a directed graph. + * *

- * Tarjan's algorithm is a linear time algorithm to find the strongly connected components of a -directed graph, which, from here onwards will be referred as SCC. - - * A graph is said to be strongly connected if every vertex is reachable from every other vertex. -The SCCs of a directed graph form a partition into subgraphs that are themselves strongly -connected. Single node is always a SCC. - - * Example: -0 --------> 1 -------> 3 --------> 4 -^ / -| / -| / -| / -| / -| / -| / -| / -| / -| / -|V -2 - -For the above graph, the SCC list goes as follows: -1, 2, 0 -3 -4 - -We can also see that order of the nodes in an SCC doesn't matter since they are in cycle. - -{@summary} -Tarjan's Algorithm: - * DFS search produces a DFS tree - * Strongly Connected Components form subtrees of the DFS tree. - * If we can find the head of these subtrees, we can get all the nodes in that subtree (including -the head) and that will be one SCC. - * There is no back edge from one SCC to another (here can be cross edges, but they will not be -used). - - * Kosaraju Algorithm aims at doing the same but uses two DFS traversalse whereas Tarjan’s -algorithm does the same in a single DFS, which leads to much lower constant factors in the latter. - + * Tarjan's algorithm is a linear time algorithm (O(V + E)) that identifies the SCCs of a directed graph. + * An SCC is a maximal subgraph where every vertex is reachable from every other vertex within the subgraph. + * + *

Algorithm Overview:

+ *
    + *
  • DFS Search: A depth-first search (DFS) is performed on the graph to generate a DFS tree.
  • + *
  • Identification of SCCs: SCCs correspond to subtrees within this DFS tree.
  • + *
  • Low-Link Values: For each node, a low-link value is maintained, which indicates the earliest visited + * vertex (the one with the minimum insertion time) that can be reached from that subtree.
  • + *
  • Stack Usage: Nodes are stored in a stack during DFS. When an SCC is identified, nodes are popped from + * the stack until the head of the SCC is reached.
  • + *
+ * + *

+ * Example of a directed graph: + *

+ *  0 --------> 1 -------> 3 --------> 4
+ *  ^          /
+ *  |         /
+ *  |        /
+ *  |       /
+ *  |      /
+ *  |     /
+ *  |    /
+ *  |   /
+ *  |  /
+ *  | /
+ *  V
+ *  2
+ * 
+ * + *

+ * For the above graph, the SCC list is as follows: + *

    + *
  • 1, 2, 0
  • + *
  • 3
  • + *
  • 4
  • + *
+ * The order of nodes in an SCC does not matter as they form cycles. + * + *

Comparison with Kosaraju's Algorithm:

+ *

+ * Kosaraju's algorithm also identifies SCCs but does so using two DFS traversals. + * In contrast, Tarjan's algorithm achieves this in a single DFS traversal, leading to improved performance + * in terms of constant factors. + *

*/ public class TarjansAlgorithm { - // Timer for tracking lowtime and insertion time + // Timer for tracking low time and insertion time private int time; - private final List> sccList = new ArrayList>(); + // List to store all strongly connected components + private final List> sccList = new ArrayList<>(); + /** + * Finds and returns the strongly connected components (SCCs) of the directed graph. + * + * @param v the number of vertices in the graph + * @param graph the adjacency list representation of the graph + * @return a list of lists, where each inner list represents a strongly connected component + */ public List> stronglyConnectedComponents(int v, List> graph) { - - // Initially all vertices as unvisited, insertion and low time are undefined - - // insertionTime:Time when a node is visited 1st time while DFS traversal - - // lowTime: indicates the earliest visited vertex (the vertex with minimum insertion time) - // that can be reached from a subtree rooted with a particular node. + // Initialize arrays for insertion time and low-link values int[] lowTime = new int[v]; int[] insertionTime = new int[v]; for (int i = 0; i < v; i++) { @@ -72,11 +79,11 @@ public List> stronglyConnectedComponents(int v, List lowTime[i] = -1; } - // To check if element is present in stack + // Track if vertices are in the stack boolean[] isInStack = new boolean[v]; - // Store nodes during DFS - Stack st = new Stack(); + // Stack to hold nodes during DFS + Stack st = new Stack<>(); for (int i = 0; i < v; i++) { if (insertionTime[i] == -1) { @@ -87,36 +94,44 @@ public List> stronglyConnectedComponents(int v, List return sccList; } + /** + * A utility function to perform DFS and find SCCs. + * + * @param u the current vertex being visited + * @param lowTime array to keep track of the low-link values + * @param insertionTime array to keep track of the insertion times + * @param isInStack boolean array indicating if a vertex is in the stack + * @param st the stack used for DFS + * @param graph the adjacency list representation of the graph + */ private void stronglyConnCompsUtil(int u, int[] lowTime, int[] insertionTime, boolean[] isInStack, Stack st, List> graph) { - - // Initialize insertion time and lowTime value of current node + // Set insertion time and low-link value insertionTime[u] = time; lowTime[u] = time; - time += 1; + time++; - // Push current node into stack + // Push current node onto the stack isInStack[u] = true; st.push(u); - // Go through all vertices adjacent to this + // Explore adjacent vertices for (Integer vertex : graph.get(u)) { - // If the adjacent node is unvisited, do DFS if (insertionTime[vertex] == -1) { stronglyConnCompsUtil(vertex, lowTime, insertionTime, isInStack, st, graph); - // update lowTime for the current node comparing lowtime of adj node + // Update low-link value lowTime[u] = Math.min(lowTime[u], lowTime[vertex]); } else if (isInStack[vertex]) { - // If adj node is in stack, update low + // Vertex is in the stack; update low-link value lowTime[u] = Math.min(lowTime[u], insertionTime[vertex]); } } - // If lowtime and insertion time are same, current node is the head of an SCC - // head node found, get all the nodes in this SCC + + // Check if the current vertex is the root of an SCC if (lowTime[u] == insertionTime[u]) { int w = -1; - var scc = new ArrayList(); + List scc = new ArrayList<>(); - // Stack has all the nodes of the current SCC + // Pop vertices from the stack until the root is found while (w != u) { w = st.pop(); scc.add(w); diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/UndirectedAdjacencyListGraph.java b/src/main/java/com/thealgorithms/datastructures/graphs/UndirectedAdjacencyListGraph.java new file mode 100644 index 000000000000..8aafc1ef3368 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/graphs/UndirectedAdjacencyListGraph.java @@ -0,0 +1,69 @@ +package com.thealgorithms.datastructures.graphs; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +public class UndirectedAdjacencyListGraph { + private ArrayList> adjacencyList = new ArrayList<>(); + + /** + * Adds a new node to the graph by adding an empty HashMap for its neighbors. + * @return the index of the newly added node in the adjacency list + */ + public int addNode() { + adjacencyList.add(new HashMap<>()); + return adjacencyList.size() - 1; + } + + /** + * Adds an undirected edge between the origin node (@orig) and the destination node (@dest) with the specified weight. + * If the edge already exists, no changes are made. + * @param orig the index of the origin node + * @param dest the index of the destination node + * @param weight the weight of the edge between @orig and @dest + * @return true if the edge was successfully added, false if the edge already exists or if any node index is invalid + */ + public boolean addEdge(int orig, int dest, int weight) { + int numNodes = adjacencyList.size(); + if (orig >= numNodes || dest >= numNodes || orig < 0 || dest < 0) { + return false; + } + + if (adjacencyList.get(orig).containsKey(dest)) { + return false; + } + + adjacencyList.get(orig).put(dest, weight); + adjacencyList.get(dest).put(orig, weight); + return true; + } + + /** + * Returns the set of all adjacent nodes (neighbors) for the given node. + * @param node the index of the node whose neighbors are to be retrieved + * @return a HashSet containing the indices of all neighboring nodes + */ + public HashSet getNeighbors(int node) { + return new HashSet<>(adjacencyList.get(node).keySet()); + } + + /** + * Returns the weight of the edge between the origin node (@orig) and the destination node (@dest). + * If no edge exists, returns null. + * @param orig the index of the origin node + * @param dest the index of the destination node + * @return the weight of the edge between @orig and @dest, or null if no edge exists + */ + public Integer getEdgeWeight(int orig, int dest) { + return adjacencyList.get(orig).getOrDefault(dest, null); + } + + /** + * Returns the number of nodes currently in the graph. + * @return the number of nodes in the graph + */ + public int size() { + return adjacencyList.size(); + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/WelshPowell.java b/src/main/java/com/thealgorithms/datastructures/graphs/WelshPowell.java index 0981638d4903..26ca97736fe9 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/WelshPowell.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/WelshPowell.java @@ -5,21 +5,41 @@ import java.util.HashSet; import java.util.stream.IntStream; -/* - * The Welsh-Powell algorithm is a graph coloring algorithm - * used for coloring a graph with the minimum number of colors. - * https://en.wikipedia.org/wiki/Graph_coloring +/** + * The Welsh-Powell algorithm is a graph coloring algorithm that aims to color a graph + * using the minimum number of colors such that no two adjacent vertices share the same color. + * + *

+ * The algorithm works by: + *

    + *
  1. Sorting the vertices in descending order based on their degrees (number of edges connected).
  2. + *
  3. Iterating through each vertex and assigning it the smallest available color that has not been used by its adjacent vertices.
  4. + *
  5. Coloring adjacent vertices with the same color is avoided.
  6. + *
+ *

+ * + *

+ * For more information, see Graph Coloring. + *

*/ - public final class WelshPowell { - private static final int BLANK_COLOR = -1; // Representing uncolored state + private static final int BLANK_COLOR = -1; // Constant representing an uncolored state private WelshPowell() { } + /** + * Represents a graph using an adjacency list. + */ static final class Graph { - private HashSet[] adjacencyLists; - + private final HashSet[] adjacencyLists; + + /** + * Initializes a graph with a specified number of vertices. + * + * @param vertices the number of vertices in the graph + * @throws IllegalArgumentException if the number of vertices is negative + */ private Graph(int vertices) { if (vertices < 0) { throw new IllegalArgumentException("Number of vertices cannot be negative"); @@ -29,6 +49,13 @@ private Graph(int vertices) { Arrays.setAll(adjacencyLists, i -> new HashSet<>()); } + /** + * Adds an edge between two vertices in the graph. + * + * @param nodeA one end of the edge + * @param nodeB the other end of the edge + * @throws IllegalArgumentException if the vertices are out of bounds or if a self-loop is attempted + */ private void addEdge(int nodeA, int nodeB) { validateVertex(nodeA); validateVertex(nodeB); @@ -39,21 +66,46 @@ private void addEdge(int nodeA, int nodeB) { adjacencyLists[nodeB].add(nodeA); } + /** + * Validates that the vertex index is within the bounds of the graph. + * + * @param vertex the index of the vertex to validate + * @throws IllegalArgumentException if the vertex is out of bounds + */ private void validateVertex(int vertex) { if (vertex < 0 || vertex >= getNumVertices()) { throw new IllegalArgumentException("Vertex " + vertex + " is out of bounds"); } } + /** + * Returns the adjacency list for a specific vertex. + * + * @param vertex the index of the vertex + * @return the set of adjacent vertices + */ HashSet getAdjacencyList(int vertex) { return adjacencyLists[vertex]; } + /** + * Returns the number of vertices in the graph. + * + * @return the number of vertices + */ int getNumVertices() { return adjacencyLists.length; } } + /** + * Creates a graph with the specified number of vertices and edges. + * + * @param numberOfVertices the total number of vertices + * @param listOfEdges a 2D array representing edges where each inner array contains two vertex indices + * @return a Graph object representing the created graph + * @throws IllegalArgumentException if the edge array is invalid or vertices are out of bounds + */ public static Graph makeGraph(int numberOfVertices, int[][] listOfEdges) { Graph graph = new Graph(numberOfVertices); for (int[] edge : listOfEdges) { @@ -65,6 +117,12 @@ public static Graph makeGraph(int numberOfVertices, int[][] listOfEdges) { return graph; } + /** + * Finds the coloring of the given graph using the Welsh-Powell algorithm. + * + * @param graph the input graph to color + * @return an array of integers where each index represents a vertex and the value represents the color assigned + */ public static int[] findColoring(Graph graph) { int[] colors = initializeColors(graph.getNumVertices()); Integer[] sortedVertices = getSortedNodes(graph); @@ -83,30 +141,70 @@ public static int[] findColoring(Graph graph) { return colors; } + /** + * Helper method to check if a color is unassigned + * + * @param color the color to check + * @return {@code true} if the color is unassigned, {@code false} otherwise + */ private static boolean isBlank(int color) { return color == BLANK_COLOR; } + /** + * Checks if a vertex has adjacent colored vertices + * + * @param graph the input graph + * @param vertex the vertex to check + * @param colors the array of colors assigned to the vertices + * @return {@code true} if the vertex has adjacent colored vertices, {@code false} otherwise + */ private static boolean isAdjacentToColored(Graph graph, int vertex, int[] colors) { return graph.getAdjacencyList(vertex).stream().anyMatch(otherVertex -> !isBlank(colors[otherVertex])); } + /** + * Initializes the colors array with blank color + * + * @param numberOfVertices the number of vertices in the graph + * @return an array of integers representing the colors assigned to the vertices + */ private static int[] initializeColors(int numberOfVertices) { int[] colors = new int[numberOfVertices]; Arrays.fill(colors, BLANK_COLOR); return colors; } + /** + * Sorts the vertices by their degree in descending order + * + * @param graph the input graph + * @return an array of integers representing the vertices sorted by degree + */ private static Integer[] getSortedNodes(final Graph graph) { return IntStream.range(0, graph.getNumVertices()).boxed().sorted(Comparator.comparingInt(v -> - graph.getAdjacencyList(v).size())).toArray(Integer[] ::new); } + /** + * Computes the colors already used by the adjacent vertices + * + * @param graph the input graph + * @param vertex the vertex to check + * @param colors the array of colors assigned to the vertices + * @return an array of booleans representing the colors used by the adjacent vertices + */ private static boolean[] computeUsedColors(final Graph graph, final int vertex, final int[] colors) { boolean[] usedColors = new boolean[graph.getNumVertices()]; graph.getAdjacencyList(vertex).stream().map(neighbor -> colors[neighbor]).filter(color -> !isBlank(color)).forEach(color -> usedColors[color] = true); return usedColors; } + /** + * Finds the first unused color + * + * @param usedColors the array of colors used by the adjacent vertices + * @return the first unused color + */ private static int firstUnusedColor(boolean[] usedColors) { return IntStream.range(0, usedColors.length).filter(color -> !usedColors[color]).findFirst().getAsInt(); } diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java index 416cee99d028..3637e323f097 100644 --- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java +++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java @@ -2,23 +2,45 @@ import java.util.LinkedList; -// implementation of generic hashmaps using array of Linked Lists - +/** + * A generic implementation of a hash map using an array of linked lists for collision resolution. + * This class provides a way to store key-value pairs efficiently, allowing for average-case + * constant time complexity for insertion, deletion, and retrieval operations. + * + *

+ * The hash map uses separate chaining for collision resolution. Each bucket in the hash map is a + * linked list that stores nodes containing key-value pairs. When a collision occurs (i.e., when + * two keys hash to the same index), the new key-value pair is simply added to the corresponding + * linked list. + *

+ * + *

+ * The hash map automatically resizes itself when the load factor exceeds 0.75. The load factor is + * defined as the ratio of the number of entries to the number of buckets. When resizing occurs, + * all existing entries are rehashed and inserted into the new buckets. + *

+ * + * @param the type of keys maintained by this hash map + * @param the type of mapped values + */ public class GenericHashMapUsingArray { - private int size; // n (total number of key-value pairs) - private LinkedList[] buckets; // N = buckets.length - private float lf = 0.75f; + private int size; // Total number of key-value pairs + private LinkedList[] buckets; // Array of linked lists (buckets) for storing entries + /** + * Constructs a new empty hash map with an initial capacity of 16. + */ public GenericHashMapUsingArray() { initBuckets(16); size = 0; } - // load factor = 0.75 means if we need to add 100 items and we have added - // 75, then adding 76th item it will double the size, copy all elements - // & then add 76th item. - + /** + * Initializes the buckets for the hash map with the specified number of buckets. + * + * @param n the number of buckets to initialize + */ private void initBuckets(int n) { buckets = new LinkedList[n]; for (int i = 0; i < buckets.length; i++) { @@ -26,43 +48,66 @@ private void initBuckets(int n) { } } + /** + * Associates the specified value with the specified key in this map. + * If the map previously contained a mapping for the key, the old value is replaced. + * + * @param key the key with which the specified value is to be associated + * @param value the value to be associated with the specified key + */ public void put(K key, V value) { int bucketIndex = hashFunction(key); LinkedList nodes = buckets[bucketIndex]; - for (Node node : nodes) { // if key present => update + // Update existing key's value if present + for (Node node : nodes) { if (node.key.equals(key)) { node.value = value; return; } } - // key is not present => insert + // Insert new key-value pair nodes.add(new Node(key, value)); size++; - if ((float) size / buckets.length > lf) { + // Check if rehashing is needed + // Load factor threshold for resizing + float loadFactorThreshold = 0.75f; + if ((float) size / buckets.length > loadFactorThreshold) { reHash(); } } - // tells which bucket to go to + /** + * Returns the index of the bucket in which the key would be stored. + * + * @param key the key whose bucket index is to be computed + * @return the bucket index + */ private int hashFunction(K key) { return Math.floorMod(key.hashCode(), buckets.length); } + /** + * Rehashes the map by doubling the number of buckets and re-inserting all entries. + */ private void reHash() { - System.out.println("Rehashing!"); - LinkedList[] old = buckets; - initBuckets(old.length * 2); + LinkedList[] oldBuckets = buckets; + initBuckets(oldBuckets.length * 2); this.size = 0; - for (LinkedList nodes : old) { + for (LinkedList nodes : oldBuckets) { for (Node node : nodes) { put(node.key, node.value); } } } + /** + * Removes the mapping for the specified key from this map if present. + * + * @param key the key whose mapping is to be removed from the map + */ public void remove(K key) { int bucketIndex = hashFunction(key); LinkedList nodes = buckets[bucketIndex]; @@ -74,14 +119,28 @@ public void remove(K key) { break; } } - nodes.remove(target); - size--; + + if (target != null) { + nodes.remove(target); + size--; + } } + /** + * Returns the number of key-value pairs in this map. + * + * @return the number of key-value pairs + */ public int size() { return this.size; } + /** + * Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key. + * + * @param key the key whose associated value is to be returned + * @return the value associated with the specified key, or null if no mapping exists + */ public V get(K key) { int bucketIndex = hashFunction(key); LinkedList nodes = buckets[bucketIndex]; @@ -96,7 +155,6 @@ public V get(K key) { @Override public String toString() { StringBuilder builder = new StringBuilder(); - builder.append("{"); for (LinkedList nodes : buckets) { for (Node node : nodes) { @@ -106,19 +164,37 @@ public String toString() { builder.append(", "); } } + // Remove trailing comma and space + if (builder.length() > 1) { + builder.setLength(builder.length() - 2); + } builder.append("}"); return builder.toString(); } + /** + * Returns true if this map contains a mapping for the specified key. + * + * @param key the key whose presence in this map is to be tested + * @return true if this map contains a mapping for the specified key + */ public boolean containsKey(K key) { return get(key) != null; } + /** + * A private class representing a key-value pair (node) in the hash map. + */ public class Node { - K key; V value; + /** + * Constructs a new Node with the specified key and value. + * + * @param key the key of the key-value pair + * @param value the value of the key-value pair + */ public Node(K key, V value) { this.key = key; this.value = value; diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayList.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayList.java index a1ef457f3432..89e25f4eb0f7 100644 --- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayList.java +++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayList.java @@ -3,12 +3,34 @@ import java.util.ArrayList; import java.util.LinkedList; +/** + * A generic implementation of a hash map using an array list of linked lists for collision resolution. + * This class allows storage of key-value pairs with average-case constant time complexity for insertion, + * deletion, and retrieval operations. + * + *

+ * The hash map uses separate chaining to handle collisions. Each bucket in the hash map is represented + * by a linked list that holds nodes containing key-value pairs. When multiple keys hash to the same index, + * they are stored in the same linked list. + *

+ * + *

+ * The hash map automatically resizes itself when the load factor exceeds 0.5. The load factor is defined + * as the ratio of the number of entries to the number of buckets. When resizing occurs, all existing entries + * are rehashed and inserted into the new buckets. + *

+ * + * @param the type of keys maintained by this hash map + * @param the type of mapped values + */ public class GenericHashMapUsingArrayList { - ArrayList> buckets; - private float lf = 0.5f; - private int size; + private ArrayList> buckets; // Array list of buckets (linked lists) + private int size; // Number of key-value pairs in the hash map + /** + * Constructs a new empty hash map with an initial capacity of 10 buckets. + */ public GenericHashMapUsingArrayList() { buckets = new ArrayList<>(); for (int i = 0; i < 10; i++) { @@ -17,6 +39,13 @@ public GenericHashMapUsingArrayList() { size = 0; } + /** + * Associates the specified value with the specified key in this map. + * If the map previously contained a mapping for the key, the old value is replaced. + * + * @param key the key with which the specified value is to be associated + * @param value the value to be associated with the specified key + */ public void put(K key, V value) { int hash = Math.abs(key.hashCode() % buckets.size()); LinkedList nodes = buckets.get(hash); @@ -31,25 +60,36 @@ public void put(K key, V value) { nodes.add(new Node(key, value)); size++; - if ((float) size / buckets.size() > lf) { + // Load factor threshold for resizing + float loadFactorThreshold = 0.5f; + if ((float) size / buckets.size() > loadFactorThreshold) { reHash(); } } + /** + * Resizes the hash map by doubling the number of buckets and rehashing existing entries. + */ private void reHash() { - ArrayList> old = buckets; + ArrayList> oldBuckets = buckets; buckets = new ArrayList<>(); size = 0; - for (int i = 0; i < old.size() * 2; i++) { + for (int i = 0; i < oldBuckets.size() * 2; i++) { buckets.add(new LinkedList<>()); } - for (LinkedList nodes : buckets) { + for (LinkedList nodes : oldBuckets) { for (Node node : nodes) { put(node.key, node.val); } } } + /** + * Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key. + * + * @param key the key whose associated value is to be returned + * @return the value associated with the specified key, or null if no mapping exists + */ public V get(K key) { int hash = Math.abs(key.hashCode() % buckets.size()); LinkedList nodes = buckets.get(hash); @@ -61,6 +101,11 @@ public V get(K key) { return null; } + /** + * Removes the mapping for the specified key from this map if present. + * + * @param key the key whose mapping is to be removed from the map + */ public void remove(K key) { int hash = Math.abs(key.hashCode() % buckets.size()); LinkedList nodes = buckets.get(hash); @@ -72,18 +117,36 @@ public void remove(K key) { break; } } - nodes.remove(target); - size--; + if (target != null) { + nodes.remove(target); + size--; + } } + /** + * Returns true if this map contains a mapping for the specified key. + * + * @param key the key whose presence in this map is to be tested + * @return true if this map contains a mapping for the specified key + */ public boolean containsKey(K key) { return get(key) != null; } + /** + * Returns the number of key-value pairs in this map. + * + * @return the number of key-value pairs + */ public int size() { return this.size; } + /** + * Returns a string representation of the map, containing all key-value pairs. + * + * @return a string representation of the map + */ @Override public String toString() { StringBuilder builder = new StringBuilder(); @@ -96,15 +159,27 @@ public String toString() { builder.append(", "); } } + // Remove trailing comma and space if there are any elements + if (builder.length() > 1) { + builder.setLength(builder.length() - 2); + } builder.append("}"); return builder.toString(); } + /** + * A private inner class representing a key-value pair (node) in the hash map. + */ private class Node { - K key; V val; + /** + * Constructs a new Node with the specified key and value. + * + * @param key the key of the key-value pair + * @param val the value of the key-value pair + */ Node(K key, V val) { this.key = key; this.val = val; diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java index 1aae122b48ec..aed39c941430 100644 --- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java +++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java @@ -85,6 +85,28 @@ public void display() { } } + /** + * Clears the contents of the hash map by reinitializing each bucket. + */ + public void clear() { + for (int i = 0; i < hashSize; i++) { + buckets[i] = new LinkedList<>(); + } + } + + /** + * Gets the number of key-value pairs in the hash map. + * + * @return the number of key-value pairs in the hash map + */ + public int size() { + int size = 0; + for (int i = 0; i < hashSize; i++) { + size += buckets[i].isEmpty() ? 0 : 1; + } + return size; + } + /** * A nested static class that represents a linked list used for separate chaining in the hash map. * diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashing.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashing.java index a67968d7e659..8bcf00730acb 100644 --- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashing.java +++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashing.java @@ -3,25 +3,26 @@ import java.util.Objects; /** - * This class is an implementation of a hash table using Cuckoo Hashing It uses - * a dynamic array to lengthen the size of the hash table when load factor > .7 + * This class implements a hash table using Cuckoo Hashing. + * Cuckoo hashing is a type of open-addressing hash table that resolves collisions + * by relocating existing keys. It utilizes two hash functions to minimize collisions + * and automatically resizes the table when the load factor exceeds 0.7. * - * ... + * For more information on cuckoo hashing, refer to + * this Wikipedia page. */ public class HashMapCuckooHashing { - private int tableSize; // size of the hash table - private Integer[] buckets; // array representing the table - private final Integer emptySlot; - private int size; // number of elements in the hash table - - private int thresh; // threshold for infinite loop checking + private int tableSize; // Size of the hash table + private Integer[] buckets; // Array representing the hash table + private final Integer emptySlot; // Placeholder for deleted slots + private int size; // Number of elements in the hash table + private int thresh; // Threshold for detecting infinite loops during insertion /** - * Constructor initializes buckets array, hsize, and creates dummy object - * for emptySlot + * Constructs a HashMapCuckooHashing object with the specified initial table size. * - * @param tableSize the desired size of the hash map + * @param tableSize the initial size of the hash map */ public HashMapCuckooHashing(int tableSize) { this.buckets = new Integer[tableSize]; @@ -32,13 +33,11 @@ public HashMapCuckooHashing(int tableSize) { } /** - * The 2 Hash Functions takes a given key and finds an index based on its data, 2 distinctive - * ways to minimize collisions + * Computes the first hash index for a given key using the modulo operation. * - * @param key the desired key to be converted - * @return int an index corresponding to the key + * @param key the key for which the hash index is computed + * @return an integer index corresponding to the key */ - public int hashFunction1(int key) { int hash = key % tableSize; if (hash < 0) { @@ -47,6 +46,12 @@ public int hashFunction1(int key) { return hash; } + /** + * Computes the second hash index for a given key using integer division. + * + * @param key the key for which the hash index is computed + * @return an integer index corresponding to the key + */ public int hashFunction2(int key) { int hash = key / tableSize; hash %= tableSize; @@ -57,14 +62,14 @@ public int hashFunction2(int key) { } /** - * inserts the key into the hash map by wrapping it as an Integer object, then uses while loop - * to insert new key if desired place is empty, return. if already occupied, continue while loop - * over the new key that has just been pushed out. if while loop continues more than Thresh, - * rehash table to new size, then push again. + * Inserts a key into the hash table using cuckoo hashing. + * If the target bucket is occupied, it relocates the existing key and attempts to insert + * it into its alternate location. If the insertion process exceeds the threshold, + * the table is resized. * - * @param key the desired key to be inserted in the hash map + * @param key the key to be inserted into the hash table + * @throws IllegalArgumentException if the key already exists in the table */ - public void insertKey2HashTable(int key) { Integer wrappedInt = key; Integer temp; @@ -77,7 +82,7 @@ public void insertKey2HashTable(int key) { } if (checkTableContainsKey(key)) { - throw new IllegalArgumentException("Key already inside, no duplicates allowed"); + throw new IllegalArgumentException("Key already exists; duplicates are not allowed."); } while (loopCounter <= thresh) { @@ -117,9 +122,7 @@ public void insertKey2HashTable(int key) { } /** - * creates new HashMapCuckooHashing object, then inserts each of the elements in the previous - * table to it with its new hash functions. then refers current array to new table. - * + * Rehashes the current table to a new size (double the current size) and reinserts existing keys. */ public void reHashTableIncreasesTableSize() { HashMapCuckooHashing newT = new HashMapCuckooHashing(tableSize * 2); @@ -134,15 +137,16 @@ public void reHashTableIncreasesTableSize() { } /** - * deletes a key from the hash map and adds an available placeholder + * Deletes a key from the hash table, marking its position as available. * - * @param key the desired key to be deleted + * @param key the key to be deleted from the hash table + * @throws IllegalArgumentException if the table is empty or if the key is not found */ public void deleteKeyFromHashTable(int key) { Integer wrappedInt = key; int hash = hashFunction1(key); if (isEmpty()) { - throw new IllegalArgumentException("Table is empty"); + throw new IllegalArgumentException("Table is empty, cannot delete."); } if (Objects.equals(buckets[hash], wrappedInt)) { @@ -157,11 +161,11 @@ public void deleteKeyFromHashTable(int key) { size--; return; } - throw new IllegalArgumentException("Key " + key + " already inside, no duplicates allowed"); + throw new IllegalArgumentException("Key " + key + " not found in the table."); } /** - * Displays the hash table line by line + * Displays the hash table contents, bucket by bucket. */ public void displayHashtable() { for (int i = 0; i < tableSize; i++) { @@ -175,17 +179,18 @@ public void displayHashtable() { } /** - * Finds the index of location based on an inputted key + * Finds the index of a given key in the hash table. * - * @param key the desired key to be found - * @return int the index where the key is located + * @param key the key to be found + * @return the index where the key is located + * @throws IllegalArgumentException if the table is empty or the key is not found */ public int findKeyInTable(int key) { Integer wrappedInt = key; int hash = hashFunction1(key); if (isEmpty()) { - throw new IllegalArgumentException("Table is empty"); + throw new IllegalArgumentException("Table is empty; cannot find keys."); } if (Objects.equals(buckets[hash], wrappedInt)) { @@ -194,66 +199,70 @@ public int findKeyInTable(int key) { hash = hashFunction2(key); if (!Objects.equals(buckets[hash], wrappedInt)) { - throw new IllegalArgumentException("Key " + key + " not found in table"); + throw new IllegalArgumentException("Key " + key + " not found in the table."); } else { return hash; } } /** - * checks if key is inside without any output other than returned boolean. + * Checks if the given key is present in the hash table. * - * @param key the desired key to be found - * @return int the index where the key is located + * @param key the key to be checked + * @return true if the key exists, false otherwise */ public boolean checkTableContainsKey(int key) { - return ((buckets[hashFunction1(key)] != null && buckets[hashFunction1(key)].equals(key)) || (buckets[hashFunction2(key)] != null && buckets[hashFunction2(key)] == key)); + return ((buckets[hashFunction1(key)] != null && buckets[hashFunction1(key)].equals(key)) || (buckets[hashFunction2(key)] != null && buckets[hashFunction2(key)].equals(key))); } /** - * Checks the load factor of the hash table if greater than .7, - * automatically lengthens table to prevent further collisions + * Checks the load factor of the hash table. If the load factor exceeds 0.7, + * the table is resized to prevent further collisions. + * + * @return the current load factor of the hash table */ public double checkLoadFactor() { double factor = (double) size / tableSize; if (factor > .7) { - System.out.printf("Load factor is %.2f , rehashing table%n", factor); + System.out.printf("Load factor is %.2f, rehashing table.%n", factor); reHashTableIncreasesTableSize(); } return factor; } /** - * isFull returns true if the hash map is full and false if not full + * Checks if the hash map is full. * - * @return boolean is Empty + * @return true if the hash map is full, false otherwise */ public boolean isFull() { - boolean response = true; for (int i = 0; i < tableSize; i++) { if (buckets[i] == null || Objects.equals(buckets[i], emptySlot)) { return false; } } - return response; + return true; } /** - * isEmpty returns true if the hash map is empty and false if not empty + * Checks if the hash map is empty. * - * @return boolean is Empty + * @return true if the hash map is empty, false otherwise */ public boolean isEmpty() { - boolean response = true; for (int i = 0; i < tableSize; i++) { if (buckets[i] != null) { - response = false; - break; + return false; } } - return response; + return true; } + /** + * Returns the current number of keys in the hash table. + * + * @return the number of keys present in the hash table + */ public int getNumberOfKeysInTable() { return size; } diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Intersection.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Intersection.java index 54bd10de50fa..0e49218d6348 100644 --- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Intersection.java +++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Intersection.java @@ -1,27 +1,57 @@ package com.thealgorithms.datastructures.hashmap.hashing; -/* - * this is algo which implies common mathematical set theory concept - * called intersection in which result is common values of both the sets - * here metaphor of sets is HashMap - */ - import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +/** + * The {@code Intersection} class provides a method to compute the intersection of two integer arrays. + * The intersection is defined as the set of common elements present in both arrays. + *

+ * This class utilizes a HashMap to efficiently count occurrences of elements in the first array, + * allowing for an efficient lookup of common elements in the second array. + *

+ * + *

+ * Example: + *

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

+ * + *

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

+ */ public final class Intersection { + /** + * Computes the intersection of two integer arrays. + * Steps: + * 1. Count the occurrences of each element in the first array using a HashMap. + * 2. Iterate over the second array and check if the element is present in the HashMap. + * If it is, add it to the result list and decrement the count in the HashMap. + * 3. Return the result list containing the intersection of the two arrays. + * + * @param arr1 the first array of integers + * @param arr2 the second array of integers + * @return a list containing the intersection of the two arrays, or an empty list if either array is null or empty + */ public static List intersection(int[] arr1, int[] arr2) { if (arr1 == null || arr2 == null || arr1.length == 0 || arr2.length == 0) { return Collections.emptyList(); } + Map cnt = new HashMap<>(16); for (int v : arr1) { cnt.put(v, cnt.getOrDefault(v, 0) + 1); } + List res = new ArrayList<>(); for (int v : arr2) { if (cnt.containsKey(v) && cnt.get(v) > 0) { diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMap.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMap.java index c96da27c0331..10d5dc7decae 100644 --- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMap.java +++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMap.java @@ -2,24 +2,51 @@ import java.util.ArrayList; -/*** - * This class is an implementation of a hash table using linear probing. +/** + * This class implements a hash table using linear probing to resolve collisions. + * Linear probing is a collision resolution method where each slot in the hash table is checked in a sequential manner + * until an empty slot is found. + * + *

+ * The class allows for storing key-value pairs, where both the key and value are generic types. + * The key must be of a type that implements the Comparable interface to ensure that the keys can be compared for sorting. + *

+ * + *

+ * This implementation supports basic operations such as: + *

    + *
  • put(Key key, Value value): Adds a key-value pair to the hash table. If the key already exists, its value is updated.
  • + *
  • get(Key key): Retrieves the value associated with the given key.
  • + *
  • delete(Key key): Removes the key and its associated value from the hash table.
  • + *
  • contains(Key key): Checks if the hash table contains a given key.
  • + *
  • size(): Returns the number of key-value pairs in the hash table.
  • + *
  • keys(): Returns an iterable collection of keys stored in the hash table.
  • + *
+ *

+ * + *

+ * The internal size of the hash table is automatically resized when the load factor exceeds 0.5 or falls below 0.125, + * ensuring efficient space utilization. + *

+ * * @see Linear Probing Hash Table * - * @param keys type. - * @param values type. + * @param the type of keys maintained by this map + * @param the type of mapped values */ public class LinearProbingHashMap, Value> extends Map { private int hsize; // size of the hash table - private Key[] keys; - private Value[] values; - private int size; // amount of elements in the hash table + private Key[] keys; // array to store keys + private Value[] values; // array to store values + private int size; // number of elements in the hash table + // Default constructor initializes the table with a default size of 16 public LinearProbingHashMap() { this(16); } @SuppressWarnings("unchecked") + // Constructor to initialize the hash table with a specified size public LinearProbingHashMap(int size) { this.hsize = size; keys = (Key[]) new Comparable[size]; @@ -81,7 +108,7 @@ public boolean delete(Key key) { i = increment(i); while (keys[i] != null) { - // delete keys[i] an vals[i] and reinsert + // Save the key and value for rehashing Key keyToRehash = keys[i]; Value valToRehash = values[i]; keys[i] = null; diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElement.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElement.java index 5424e14c72fd..915e4228b618 100644 --- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElement.java +++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElement.java @@ -3,19 +3,23 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; -/* -This class finds the majority element(s) in an array of integers. -A majority element is an element that appears more than or equal to n/2 times, where n is the length -of the array. -*/ + +/** + * This class provides a method to find the majority element(s) in an array of integers. + * A majority element is defined as an element that appears at least ⌊n/2⌋ times, + * where n is the length of the array. If multiple elements qualify as majority elements, + * they are all returned in a list. + */ public final class MajorityElement { private MajorityElement() { } - /* - This method returns the majority element(s) in the given array of integers. - @param nums: an array of integers - @return a list of majority elements - */ + + /** + * Returns a list of majority element(s) from the given array of integers. + * + * @param nums an array of integers + * @return a list containing the majority element(s); returns an empty list if none exist + */ public static List majority(int[] nums) { HashMap numToCount = new HashMap<>(); for (final var num : nums) { diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/FibonacciHeap.java b/src/main/java/com/thealgorithms/datastructures/heaps/FibonacciHeap.java index d248bc3316ed..7a263fc08ac5 100644 --- a/src/main/java/com/thealgorithms/datastructures/heaps/FibonacciHeap.java +++ b/src/main/java/com/thealgorithms/datastructures/heaps/FibonacciHeap.java @@ -1,5 +1,30 @@ package com.thealgorithms.datastructures.heaps; +/** + * The {@code FibonacciHeap} class implements a Fibonacci Heap data structure, + * which is a collection of trees that satisfy the minimum heap property. + * This heap allows for efficient merging of heaps, as well as faster + * decrease-key and delete operations compared to other heap data structures. + * + *

Key features of the Fibonacci Heap include: + *

    + *
  • Amortized O(1) time complexity for insert and decrease-key operations.
  • + *
  • Amortized O(log n) time complexity for delete and delete-min operations.
  • + *
  • Meld operation that combines two heaps in O(1) time.
  • + *
  • Potential function that helps analyze the amortized time complexity.
  • + *
+ * + *

This implementation maintains additional statistics such as the total number + * of link and cut operations performed during the lifetime of the heap, which can + * be accessed through static methods. + * + *

The Fibonacci Heap is composed of nodes represented by the inner class + * {@code HeapNode}. Each node maintains a key, rank, marked status, and pointers + * to its children and siblings. Nodes can be linked and cut as part of the heap + * restructuring processes. + * + * @see HeapNode + */ public class FibonacciHeap { private static final double GOLDEN_RATIO = (1 + Math.sqrt(5)) / 2; diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/GenericHeap.java b/src/main/java/com/thealgorithms/datastructures/heaps/GenericHeap.java index d546b7cc88d4..b8a289db60b9 100644 --- a/src/main/java/com/thealgorithms/datastructures/heaps/GenericHeap.java +++ b/src/main/java/com/thealgorithms/datastructures/heaps/GenericHeap.java @@ -3,45 +3,83 @@ import java.util.ArrayList; import java.util.HashMap; +/** + * A generic implementation of a max heap data structure. + * + * @param the type of elements in this heap, must extend Comparable. + */ public class GenericHeap> { - ArrayList data = new ArrayList<>(); - HashMap map = new HashMap<>(); + private final ArrayList data = new ArrayList<>(); + private final HashMap map = new HashMap<>(); + /** + * Adds an item to the heap, maintaining the heap property. + * + * @param item the item to be added + */ public void add(T item) { + if (item == null) { + throw new IllegalArgumentException("Cannot insert null into the heap."); + } + this.data.add(item); - map.put(item, this.data.size() - 1); // + map.put(item, this.data.size() - 1); upHeapify(this.data.size() - 1); } + /** + * Restores the heap property by moving the item at the given index upwards. + * + * @param ci the index of the current item + */ private void upHeapify(int ci) { int pi = (ci - 1) / 2; - if (isLarger(this.data.get(ci), this.data.get(pi)) > 0) { + if (ci > 0 && isLarger(this.data.get(ci), this.data.get(pi)) > 0) { swap(pi, ci); upHeapify(pi); } } - public void display() { - System.out.println(this.data); - } - + /** + * Returns the number of elements in the heap. + * + * @return the size of the heap + */ public int size() { return this.data.size(); } + /** + * Checks if the heap is empty. + * + * @return true if the heap is empty, false otherwise + */ public boolean isEmpty() { return this.size() == 0; } + /** + * Removes and returns the maximum item from the heap. + * + * @return the maximum item + */ public T remove() { + if (isEmpty()) { + throw new IllegalStateException("Heap is empty"); + } this.swap(0, this.size() - 1); T rv = this.data.remove(this.size() - 1); - downHeapify(0); map.remove(rv); + downHeapify(0); return rv; } + /** + * Restores the heap property by moving the item at the given index downwards. + * + * @param pi the index of the current item + */ private void downHeapify(int pi) { int lci = 2 * pi + 1; int rci = 2 * pi + 2; @@ -58,15 +96,35 @@ private void downHeapify(int pi) { } } + /** + * Retrieves the maximum item from the heap without removing it. + * + * @return the maximum item + */ public T get() { - return this.data.get(0); + if (isEmpty()) { + throw new IllegalStateException("Heap is empty"); + } + return this.data.getFirst(); } - // t has higher property then return +ve + /** + * Compares two items to determine their order. + * + * @param t the first item + * @param o the second item + * @return a positive integer if t is greater than o, negative if t is less, and zero if they are equal + */ private int isLarger(T t, T o) { return t.compareTo(o); } + /** + * Swaps two items in the heap and updates their indices in the map. + * + * @param i index of the first item + * @param j index of the second item + */ private void swap(int i, int j) { T ith = this.data.get(i); T jth = this.data.get(j); @@ -76,9 +134,16 @@ private void swap(int i, int j) { map.put(jth, i); } + /** + * Updates the priority of the specified item by restoring the heap property. + * + * @param item the item whose priority is to be updated + */ public void updatePriority(T item) { + if (!map.containsKey(item)) { + throw new IllegalArgumentException("Item not found in the heap"); + } int index = map.get(item); - // because we enter lesser value then old vale upHeapify(index); } } diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/Heap.java b/src/main/java/com/thealgorithms/datastructures/heaps/Heap.java index 63e101d9b13d..8cb93edf78f3 100644 --- a/src/main/java/com/thealgorithms/datastructures/heaps/Heap.java +++ b/src/main/java/com/thealgorithms/datastructures/heaps/Heap.java @@ -40,5 +40,5 @@ public interface Heap { * @param elementIndex int containing the position in the heap of the * element to be deleted. */ - void deleteElement(int elementIndex); + void deleteElement(int elementIndex) throws EmptyHeapException; } diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/HeapElement.java b/src/main/java/com/thealgorithms/datastructures/heaps/HeapElement.java index 7c457a340645..a9cfac7036eb 100644 --- a/src/main/java/com/thealgorithms/datastructures/heaps/HeapElement.java +++ b/src/main/java/com/thealgorithms/datastructures/heaps/HeapElement.java @@ -1,14 +1,24 @@ package com.thealgorithms.datastructures.heaps; /** - * Class for heap elements.
+ * Class representing an element in a heap. * *

- * A heap element contains two attributes: a key which will be used to build the - * tree (int or double, either primitive type or object) and any kind of - * IMMUTABLE object the user sees fit to carry any information he/she likes. Be - * aware that the use of a mutable object might jeopardize the integrity of this - * information. + * A heap element contains two attributes: a key used for ordering in the heap + * (which can be of type int or double, either as primitive types or as wrapper objects) + * and an additional immutable object that can store any supplementary information the user desires. + * Note that using mutable objects may compromise the integrity of this information. + *

+ * + *

+ * The key attribute is used to determine the order of elements in the heap, + * while the additionalInfo attribute can carry user-defined data associated with the key. + *

+ * + *

+ * This class provides multiple constructors to accommodate various key types and includes + * methods to retrieve the key and additional information. + *

* * @author Nicolas Renard */ @@ -19,9 +29,10 @@ public class HeapElement { // Constructors /** - * @param key : a number of primitive type 'double' - * @param info : any kind of IMMUTABLE object. May be null, since the - * purpose is only to carry additional information of use for the user + * Creates a HeapElement with the specified key and additional information. + * + * @param key the key of the element (primitive type double) + * @param info any immutable object containing additional information, may be null */ public HeapElement(double key, Object info) { this.key = key; @@ -29,9 +40,10 @@ public HeapElement(double key, Object info) { } /** - * @param key : a number of primitive type 'int' - * @param info : any kind of IMMUTABLE object. May be null, since the - * purpose is only to carry additional information of use for the user + * Creates a HeapElement with the specified key and additional information. + * + * @param key the key of the element (primitive type int) + * @param info any immutable object containing additional information, may be null */ public HeapElement(int key, Object info) { this.key = key; @@ -39,9 +51,10 @@ public HeapElement(int key, Object info) { } /** - * @param key : a number of object type 'Integer' - * @param info : any kind of IMMUTABLE object. May be null, since the - * purpose is only to carry additional information of use for the user + * Creates a HeapElement with the specified key and additional information. + * + * @param key the key of the element (object type Integer) + * @param info any immutable object containing additional information, may be null */ public HeapElement(Integer key, Object info) { this.key = key; @@ -49,9 +62,10 @@ public HeapElement(Integer key, Object info) { } /** - * @param key : a number of object type 'Double' - * @param info : any kind of IMMUTABLE object. May be null, since the - * purpose is only to carry additional information of use for the user + * Creates a HeapElement with the specified key and additional information. + * + * @param key the key of the element (object type Double) + * @param info any immutable object containing additional information, may be null */ public HeapElement(Double key, Object info) { this.key = key; @@ -59,7 +73,9 @@ public HeapElement(Double key, Object info) { } /** - * @param key : a number of primitive type 'double' + * Creates a HeapElement with the specified key. + * + * @param key the key of the element (primitive type double) */ public HeapElement(double key) { this.key = key; @@ -67,7 +83,9 @@ public HeapElement(double key) { } /** - * @param key : a number of primitive type 'int' + * Creates a HeapElement with the specified key. + * + * @param key the key of the element (primitive type int) */ public HeapElement(int key) { this.key = key; @@ -75,7 +93,9 @@ public HeapElement(int key) { } /** - * @param key : a number of object type 'Integer' + * Creates a HeapElement with the specified key. + * + * @param key the key of the element (object type Integer) */ public HeapElement(Integer key) { this.key = key; @@ -83,7 +103,9 @@ public HeapElement(Integer key) { } /** - * @param key : a number of object type 'Double' + * Creates a HeapElement with the specified key. + * + * @param key the key of the element (object type Double) */ public HeapElement(Double key) { this.key = key; @@ -92,46 +114,60 @@ public HeapElement(Double key) { // Getters /** - * @return the object containing the additional info provided by the user. + * Returns the object containing the additional information provided by the user. + * + * @return the additional information */ public Object getInfo() { return additionalInfo; } /** - * @return the key value of the element + * Returns the key value of the element. + * + * @return the key of the element */ public double getKey() { return key; } // Overridden object methods + /** + * Returns a string representation of the heap element. + * + * @return a string describing the key and additional information + */ + @Override public String toString() { - return "Key: " + key + " - " + additionalInfo.toString(); + return "Key: " + key + " - " + (additionalInfo != null ? additionalInfo.toString() : "No additional info"); } /** - * @param otherHeapElement + * @param o : an object to compare with the current element * @return true if the keys on both elements are identical and the * additional info objects are identical. */ @Override public boolean equals(Object o) { - if (o != null) { - if (!(o instanceof HeapElement)) { - return false; - } - HeapElement otherHeapElement = (HeapElement) o; - return ((this.key == otherHeapElement.key) && (this.additionalInfo.equals(otherHeapElement.additionalInfo))); + if (o instanceof HeapElement otherHeapElement) { + return this.key == otherHeapElement.key && (this.additionalInfo != null ? this.additionalInfo.equals(otherHeapElement.additionalInfo) : otherHeapElement.additionalInfo == null); } return false; } + /** + * Returns a hash code value for the heap element. + * + * @return a hash code value for this heap element + */ @Override public int hashCode() { - int result = 0; - result = 31 * result + (int) key; - result = 31 * result + (additionalInfo != null ? additionalInfo.hashCode() : 0); + int result = 31 * (int) key; + result += (additionalInfo != null) ? additionalInfo.hashCode() : 0; return result; } + + public String getValue() { + return additionalInfo.toString(); + } } diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/KthElementFinder.java b/src/main/java/com/thealgorithms/datastructures/heaps/KthElementFinder.java new file mode 100644 index 000000000000..7ad92e8ba3c1 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/heaps/KthElementFinder.java @@ -0,0 +1,60 @@ + +package com.thealgorithms.datastructures.heaps; + +import java.util.PriorityQueue; + +/** + * This class provides methods to find the Kth largest or Kth smallest element + * in an array using heaps. It leverages a min-heap to find the Kth largest element + * and a max-heap to find the Kth smallest element efficiently. + * + * @author Hardvan + */ +public final class KthElementFinder { + private KthElementFinder() { + } + + /** + * Finds the Kth largest element in the given array. + * Uses a min-heap of size K to track the largest K elements. + * + * Time Complexity: O(n * log(k)), where n is the size of the input array. + * Space Complexity: O(k), as we maintain a heap of size K. + * + * @param nums the input array of integers + * @param k the desired Kth position (1-indexed, i.e., 1 means the largest element) + * @return the Kth largest element in the array + */ + public static int findKthLargest(int[] nums, int k) { + PriorityQueue minHeap = new PriorityQueue<>(k); + for (int num : nums) { + minHeap.offer(num); + if (minHeap.size() > k) { + minHeap.poll(); + } + } + return minHeap.peek(); + } + + /** + * Finds the Kth smallest element in the given array. + * Uses a max-heap of size K to track the smallest K elements. + * + * Time Complexity: O(n * log(k)), where n is the size of the input array. + * Space Complexity: O(k), as we maintain a heap of size K. + * + * @param nums the input array of integers + * @param k the desired Kth position (1-indexed, i.e., 1 means the smallest element) + * @return the Kth smallest element in the array + */ + public static int findKthSmallest(int[] nums, int k) { + PriorityQueue maxHeap = new PriorityQueue<>((a, b) -> b - a); + for (int num : nums) { + maxHeap.offer(num); + if (maxHeap.size() > k) { + maxHeap.poll(); + } + } + return maxHeap.peek(); + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/LeftistHeap.java b/src/main/java/com/thealgorithms/datastructures/heaps/LeftistHeap.java index ca18673c6724..1c91d24f0fb5 100644 --- a/src/main/java/com/thealgorithms/datastructures/heaps/LeftistHeap.java +++ b/src/main/java/com/thealgorithms/datastructures/heaps/LeftistHeap.java @@ -2,24 +2,33 @@ import java.util.ArrayList; -/* - * This is a leftist heap that follows the same operations as a - * binary min heap, but may be unbalanced at times and follows a - * leftist property, in which the left side is more heavy on the - * right based on the null-path length (npl) values. +/** + * This class implements a Leftist Heap, which is a type of priority queue + * that follows similar operations to a binary min-heap but allows for + * unbalanced structures based on the leftist property. * - * Source: https://iq.opengenus.org/leftist-heap/ + *

+ * A Leftist Heap maintains the leftist property, which ensures that the + * left subtree is heavier than the right subtree based on the + * null-path length (npl) values. This allows for efficient merging + * of heaps and supports operations like insertion, extraction of + * the minimum element, and in-order traversal. + *

* + *

+ * For more information on Leftist Heaps, visit: + * OpenGenus + *

*/ - public class LeftistHeap { + // Node class representing each element in the Leftist Heap private static final class Node { private final int element; private int npl; private Node left; private Node right; - // Node constructor setting the data element and left/right pointers to null + // Node constructor that initializes the element and sets child pointers to null private Node(int element) { this.element = element; left = null; @@ -30,31 +39,45 @@ private Node(int element) { private Node root; - // Constructor + // Constructor initializing an empty Leftist Heap public LeftistHeap() { root = null; } - // Checks if heap is empty + /** + * Checks if the heap is empty. + * + * @return true if the heap is empty; false otherwise + */ public boolean isEmpty() { return root == null; } - // Resets structure to initial state + /** + * Resets the heap to its initial state, effectively clearing all elements. + */ public void clear() { - // We will put head is null - root = null; + root = null; // Set root to null to clear the heap } - // Merge function that merges the contents of another leftist heap with the - // current one + /** + * Merges the contents of another Leftist Heap into this one. + * + * @param h1 the LeftistHeap to be merged into this heap + */ public void merge(LeftistHeap h1) { - // If the present function is rhs then we ignore the merge + // Merge the current heap with the provided heap and set the provided heap's root to null root = merge(root, h1.root); h1.root = null; } - // Function merge with two Nodes a and b + /** + * Merges two nodes, maintaining the leftist property. + * + * @param a the first node + * @param b the second node + * @return the merged node maintaining the leftist property + */ public Node merge(Node a, Node b) { if (a == null) { return b; @@ -64,17 +87,17 @@ public Node merge(Node a, Node b) { return a; } - // Violates leftist property, so must do a swap + // Ensure that the leftist property is maintained if (a.element > b.element) { Node temp = a; a = b; b = temp; } - // Now we call the function merge to merge a and b + // Merge the right child of node a with node b a.right = merge(a.right, b); - // Violates leftist property so must swap here + // If left child is null, make right child the left child if (a.left == null) { a.left = a.right; a.right = null; @@ -89,14 +112,21 @@ public Node merge(Node a, Node b) { return a; } - // Function insert. Uses the merge function to add the data + /** + * Inserts a new element into the Leftist Heap. + * + * @param a the element to be inserted + */ public void insert(int a) { root = merge(new Node(a), root); } - // Returns and removes the minimum element in the heap + /** + * Extracts and removes the minimum element from the heap. + * + * @return the minimum element in the heap, or -1 if the heap is empty + */ public int extractMin() { - // If is empty return -1 if (isEmpty()) { return -1; } @@ -106,14 +136,23 @@ public int extractMin() { return min; } - // Function returning a list of an in order traversal of the data structure + /** + * Returns a list of the elements in the heap in in-order traversal. + * + * @return an ArrayList containing the elements in in-order + */ public ArrayList inOrder() { ArrayList lst = new ArrayList<>(); inOrderAux(root, lst); return new ArrayList<>(lst); } - // Auxiliary function for in_order + /** + * Auxiliary function for in-order traversal + * + * @param n the current node + * @param lst the list to store the elements in in-order + */ private void inOrderAux(Node n, ArrayList lst) { if (n == null) { return; diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/MaxHeap.java b/src/main/java/com/thealgorithms/datastructures/heaps/MaxHeap.java index 9010aae4cae5..5b4b29cf1c2d 100644 --- a/src/main/java/com/thealgorithms/datastructures/heaps/MaxHeap.java +++ b/src/main/java/com/thealgorithms/datastructures/heaps/MaxHeap.java @@ -4,128 +4,245 @@ import java.util.List; /** - * Heap tree where a node's key is higher than or equal to its parent's and - * lower than or equal to its children's. + * A Max Heap implementation where each node's key is higher than or equal to its children's keys. + * This data structure provides O(log n) time complexity for insertion and deletion operations, + * and O(1) for retrieving the maximum element. + * + * Properties: + * 1. Complete Binary Tree + * 2. Parent node's key ≥ Children nodes' keys + * 3. Root contains the maximum element + * + * Example usage: + *
+ * List elements = Arrays.asList(
+ *     new HeapElement(5, "Five"),
+ *     new HeapElement(2, "Two")
+ * );
+ * MaxHeap heap = new MaxHeap(elements);
+ * heap.insertElement(new HeapElement(7, "Seven"));
+ * HeapElement max = heap.getElement(); // Returns and removes the maximum element
+ * 
* * @author Nicolas Renard */ public class MaxHeap implements Heap { + /** The internal list that stores heap elements */ private final List maxHeap; + /** + * Constructs a new MaxHeap from a list of elements. + * Null elements in the input list are ignored. + * + * @param listElements List of HeapElement objects to initialize the heap + * @throws IllegalArgumentException if the input list is null + */ public MaxHeap(List listElements) { + if (listElements == null) { + throw new IllegalArgumentException("Input list cannot be null"); + } + maxHeap = new ArrayList<>(); + + // Safe initialization: directly add non-null elements first for (HeapElement heapElement : listElements) { if (heapElement != null) { - insertElement(heapElement); - } else { - System.out.println("Null element. Not added to heap"); + maxHeap.add(heapElement); } } - if (maxHeap.isEmpty()) { - System.out.println("No element has been added, empty heap."); + + // Heapify the array bottom-up + for (int i = maxHeap.size() / 2; i >= 0; i--) { + heapifyDown(i + 1); // +1 because heapifyDown expects 1-based index + } + } + + /** + * Maintains heap properties by moving an element down the heap. + * Similar to toggleDown but used specifically during initialization. + * + * @param elementIndex 1-based index of the element to heapify + */ + private void heapifyDown(int elementIndex) { + int largest = elementIndex - 1; + int leftChild = 2 * elementIndex - 1; + int rightChild = 2 * elementIndex; + + if (leftChild < maxHeap.size() && maxHeap.get(leftChild).getKey() > maxHeap.get(largest).getKey()) { + largest = leftChild; + } + + if (rightChild < maxHeap.size() && maxHeap.get(rightChild).getKey() > maxHeap.get(largest).getKey()) { + largest = rightChild; + } + + if (largest != elementIndex - 1) { + HeapElement swap = maxHeap.get(elementIndex - 1); + maxHeap.set(elementIndex - 1, maxHeap.get(largest)); + maxHeap.set(largest, swap); + + heapifyDown(largest + 1); } } /** - * Get the element at a given index. The key for the list is equal to index - * value - 1 + * Retrieves the element at the specified index without removing it. + * Note: The index is 1-based for consistency with heap operations. * - * @param elementIndex index - * @return heapElement + * @param elementIndex 1-based index of the element to retrieve + * @return HeapElement at the specified index + * @throws IndexOutOfBoundsException if the index is invalid */ public HeapElement getElement(int elementIndex) { if ((elementIndex <= 0) || (elementIndex > maxHeap.size())) { - throw new IndexOutOfBoundsException("Index out of heap range"); + throw new IndexOutOfBoundsException("Index " + elementIndex + " is out of heap range [1, " + maxHeap.size() + "]"); } return maxHeap.get(elementIndex - 1); } - // Get the key of the element at a given index + /** + * Retrieves the key value of an element at the specified index. + * + * @param elementIndex 1-based index of the element + * @return double value representing the key + * @throws IndexOutOfBoundsException if the index is invalid + */ private double getElementKey(int elementIndex) { if ((elementIndex <= 0) || (elementIndex > maxHeap.size())) { - throw new IndexOutOfBoundsException("Index out of heap range"); + throw new IndexOutOfBoundsException("Index " + elementIndex + " is out of heap range [1, " + maxHeap.size() + "]"); } - return maxHeap.get(elementIndex - 1).getKey(); } - // Swaps two elements in the heap + /** + * Swaps two elements in the heap. + * + * @param index1 1-based index of first element + * @param index2 1-based index of second element + */ private void swap(int index1, int index2) { HeapElement temporaryElement = maxHeap.get(index1 - 1); maxHeap.set(index1 - 1, maxHeap.get(index2 - 1)); maxHeap.set(index2 - 1, temporaryElement); } - // Toggle an element up to its right place as long as its key is lower than its parent's + /** + * Moves an element up the heap until heap properties are satisfied. + * This operation is called after insertion to maintain heap properties. + * + * @param elementIndex 1-based index of the element to move up + */ private void toggleUp(int elementIndex) { double key = maxHeap.get(elementIndex - 1).getKey(); - while (getElementKey((int) Math.floor(elementIndex / 2.0)) < key) { + while (elementIndex > 1 && getElementKey((int) Math.floor(elementIndex / 2.0)) < key) { swap(elementIndex, (int) Math.floor(elementIndex / 2.0)); elementIndex = (int) Math.floor(elementIndex / 2.0); } } - // Toggle an element down to its right place as long as its key is higher - // than any of its children's + /** + * Moves an element down the heap until heap properties are satisfied. + * This operation is called after deletion to maintain heap properties. + * + * @param elementIndex 1-based index of the element to move down + */ private void toggleDown(int elementIndex) { double key = maxHeap.get(elementIndex - 1).getKey(); - boolean wrongOrder = (key < getElementKey(elementIndex * 2)) || (key < getElementKey(Math.min(elementIndex * 2, maxHeap.size()))); - while ((2 * elementIndex <= maxHeap.size()) && wrongOrder) { - // Check whether it shall swap the element with its left child or its right one if any. - if ((2 * elementIndex < maxHeap.size()) && (getElementKey(elementIndex * 2 + 1) > getElementKey(elementIndex * 2))) { - swap(elementIndex, 2 * elementIndex + 1); - elementIndex = 2 * elementIndex + 1; + boolean wrongOrder = (2 * elementIndex <= maxHeap.size() && key < getElementKey(elementIndex * 2)) || (2 * elementIndex + 1 <= maxHeap.size() && key < getElementKey(elementIndex * 2 + 1)); + + while (2 * elementIndex <= maxHeap.size() && wrongOrder) { + int largerChildIndex; + if (2 * elementIndex + 1 <= maxHeap.size() && getElementKey(elementIndex * 2 + 1) > getElementKey(elementIndex * 2)) { + largerChildIndex = 2 * elementIndex + 1; } else { - swap(elementIndex, 2 * elementIndex); - elementIndex = 2 * elementIndex; + largerChildIndex = 2 * elementIndex; } - wrongOrder = (key < getElementKey(elementIndex * 2)) || (key < getElementKey(Math.min(elementIndex * 2, maxHeap.size()))); + + swap(elementIndex, largerChildIndex); + elementIndex = largerChildIndex; + + wrongOrder = (2 * elementIndex <= maxHeap.size() && key < getElementKey(elementIndex * 2)) || (2 * elementIndex + 1 <= maxHeap.size() && key < getElementKey(elementIndex * 2 + 1)); } } - private HeapElement extractMax() { - HeapElement result = maxHeap.get(0); - deleteElement(0); + /** + * Extracts and returns the maximum element from the heap. + * + * @return HeapElement with the highest key + * @throws EmptyHeapException if the heap is empty + */ + private HeapElement extractMax() throws EmptyHeapException { + if (maxHeap.isEmpty()) { + throw new EmptyHeapException("Cannot extract from an empty heap"); + } + HeapElement result = maxHeap.getFirst(); + deleteElement(1); return result; } + /** + * {@inheritDoc} + */ @Override - public final void insertElement(HeapElement element) { + public void insertElement(HeapElement element) { + if (element == null) { + throw new IllegalArgumentException("Cannot insert null element"); + } maxHeap.add(element); toggleUp(maxHeap.size()); } + /** + * {@inheritDoc} + */ @Override - public void deleteElement(int elementIndex) { + public void deleteElement(int elementIndex) throws EmptyHeapException { if (maxHeap.isEmpty()) { - try { - throw new EmptyHeapException("Attempt to delete an element from an empty heap"); - } catch (EmptyHeapException e) { - e.printStackTrace(); - } + throw new EmptyHeapException("Cannot delete from an empty heap"); } if ((elementIndex > maxHeap.size()) || (elementIndex <= 0)) { - throw new IndexOutOfBoundsException("Index out of heap range"); + throw new IndexOutOfBoundsException("Index " + elementIndex + " is out of heap range [1, " + maxHeap.size() + "]"); } - // The last element in heap replaces the one to be deleted - maxHeap.set(elementIndex - 1, getElement(maxHeap.size())); - maxHeap.remove(maxHeap.size()); - // Shall the new element be moved up... - if (getElementKey(elementIndex) > getElementKey((int) Math.floor(elementIndex / 2.0))) { - toggleUp(elementIndex); - } // ... or down ? - else if (((2 * elementIndex <= maxHeap.size()) && (getElementKey(elementIndex) < getElementKey(elementIndex * 2))) || ((2 * elementIndex < maxHeap.size()) && (getElementKey(elementIndex) < getElementKey(elementIndex * 2)))) { - toggleDown(elementIndex); + + // Replace with last element and remove last position + maxHeap.set(elementIndex - 1, maxHeap.getLast()); + maxHeap.removeLast(); + + // No need to toggle if we just removed the last element + if (!maxHeap.isEmpty() && elementIndex <= maxHeap.size()) { + // Determine whether to toggle up or down + if (elementIndex > 1 && getElementKey(elementIndex) > getElementKey((int) Math.floor(elementIndex / 2.0))) { + toggleUp(elementIndex); + } else { + toggleDown(elementIndex); + } } } + /** + * {@inheritDoc} + */ @Override public HeapElement getElement() throws EmptyHeapException { - try { - return extractMax(); - } catch (Exception e) { - throw new EmptyHeapException("Heap is empty. Error retrieving element", e); - } + return extractMax(); + } + + /** + * Returns the current size of the heap. + * + * @return number of elements in the heap + */ + public int size() { + return maxHeap.size(); + } + + /** + * Checks if the heap is empty. + * + * @return true if the heap contains no elements + */ + public boolean isEmpty() { + return maxHeap.isEmpty(); } } diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/MedianFinder.java b/src/main/java/com/thealgorithms/datastructures/heaps/MedianFinder.java new file mode 100644 index 000000000000..4e74aaec4a10 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/heaps/MedianFinder.java @@ -0,0 +1,59 @@ +package com.thealgorithms.datastructures.heaps; + +import java.util.PriorityQueue; + +/** + * This class maintains the median of a dynamically changing data stream using + * two heaps: a max-heap and a min-heap. The max-heap stores the smaller half + * of the numbers, and the min-heap stores the larger half. + * This data structure ensures that retrieving the median is efficient. + * + * Time Complexity: + * - Adding a number: O(log n) due to heap insertion. + * - Finding the median: O(1). + * + * Space Complexity: O(n), where n is the total number of elements added. + * + * @author Hardvan + */ +public final class MedianFinder { + MedianFinder() { + } + + private PriorityQueue minHeap = new PriorityQueue<>(); + private PriorityQueue maxHeap = new PriorityQueue<>((a, b) -> b - a); + + /** + * Adds a new number to the data stream. The number is placed in the appropriate + * heap to maintain the balance between the two heaps. + * + * @param num the number to be added to the data stream + */ + public void addNum(int num) { + if (maxHeap.isEmpty() || num <= maxHeap.peek()) { + maxHeap.offer(num); + } else { + minHeap.offer(num); + } + + if (maxHeap.size() > minHeap.size() + 1) { + minHeap.offer(maxHeap.poll()); + } else if (minHeap.size() > maxHeap.size()) { + maxHeap.offer(minHeap.poll()); + } + } + + /** + * Finds the median of the numbers added so far. If the total number of elements + * is even, the median is the average of the two middle elements. If odd, the + * median is the middle element from the max-heap. + * + * @return the median of the numbers in the data stream + */ + public double findMedian() { + if (maxHeap.size() == minHeap.size()) { + return (maxHeap.peek() + minHeap.peek()) / 2.0; + } + return maxHeap.peek(); + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/MergeKSortedArrays.java b/src/main/java/com/thealgorithms/datastructures/heaps/MergeKSortedArrays.java new file mode 100644 index 000000000000..e41711f05914 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/heaps/MergeKSortedArrays.java @@ -0,0 +1,60 @@ +package com.thealgorithms.datastructures.heaps; + +import java.util.Comparator; +import java.util.PriorityQueue; + +/** + * This class provides a method to merge multiple sorted arrays into a single sorted array. + * It utilizes a min-heap to efficiently retrieve the smallest elements from each array. + * + * Time Complexity: O(n * log k), where n is the total number of elements across all arrays + * and k is the number of arrays. + * + * Space Complexity: O(k) for the heap, where k is the number of arrays. + * + * @author Hardvan + */ +public final class MergeKSortedArrays { + private MergeKSortedArrays() { + } + + /** + * Merges k sorted arrays into one sorted array using a min-heap. + * Steps: + * 1. Create a min-heap to store elements in the format: {value, array index, element index} + * 2. Add the first element from each array to the heap + * 3. While the heap is not empty, remove the smallest element from the heap + * and add it to the result array. If there are more elements in the same array, + * add the next element to the heap. + * Continue until all elements have been processed. + * The result array will contain all elements in sorted order. + * 4. Return the result array. + * + * @param arrays a 2D array, where each subarray is sorted in non-decreasing order + * @return a single sorted array containing all elements from the input arrays + */ + public static int[] mergeKArrays(int[][] arrays) { + PriorityQueue minHeap = new PriorityQueue<>(Comparator.comparingInt(a -> a[0])); + + int totalLength = 0; + for (int i = 0; i < arrays.length; i++) { + if (arrays[i].length > 0) { + minHeap.offer(new int[] {arrays[i][0], i, 0}); + totalLength += arrays[i].length; + } + } + + int[] result = new int[totalLength]; + int index = 0; + while (!minHeap.isEmpty()) { + int[] top = minHeap.poll(); + result[index++] = top[0]; + + if (top[2] + 1 < arrays[top[1]].length) { + minHeap.offer(new int[] {arrays[top[1]][top[2] + 1], top[1], top[2] + 1}); + } + } + + return result; + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/MinHeap.java b/src/main/java/com/thealgorithms/datastructures/heaps/MinHeap.java index 46864fba0047..3a4822142b5f 100644 --- a/src/main/java/com/thealgorithms/datastructures/heaps/MinHeap.java +++ b/src/main/java/com/thealgorithms/datastructures/heaps/MinHeap.java @@ -4,8 +4,25 @@ import java.util.List; /** - * Heap tree where a node's key is higher than or equal to its parent's and - * lower than or equal to its children's. + * A Min Heap implementation where each node's key is lower than or equal to its children's keys. + * This data structure provides O(log n) time complexity for insertion and deletion operations, + * and O(1) for retrieving the minimum element. + * + * Properties: + * 1. Complete Binary Tree + * 2. Parent node's key ≤ Children nodes' keys + * 3. Root contains the minimum element + * + * Example usage: + * ```java + * List elements = Arrays.asList( + * new HeapElement(5, "Five"), + * new HeapElement(2, "Two") + * ); + * MinHeap heap = new MinHeap(elements); + * heap.insertElement(new HeapElement(1, "One")); + * HeapElement min = heap.getElement(); // Returns and removes the minimum element + * ``` * * @author Nicolas Renard */ @@ -13,113 +30,242 @@ public class MinHeap implements Heap { private final List minHeap; + /** + * Constructs a new MinHeap from a list of elements. + * Null elements in the input list are ignored with a warning message. + * + * @param listElements List of HeapElement objects to initialize the heap + * @throws IllegalArgumentException if the input list is null + */ public MinHeap(List listElements) { + if (listElements == null) { + throw new IllegalArgumentException("Input list cannot be null"); + } + minHeap = new ArrayList<>(); + + // Safe initialization: directly add elements first for (HeapElement heapElement : listElements) { if (heapElement != null) { - insertElement(heapElement); + minHeap.add(heapElement); } else { System.out.println("Null element. Not added to heap"); } } + + // Heapify the array bottom-up + for (int i = minHeap.size() / 2; i >= 0; i--) { + heapifyDown(i + 1); + } + if (minHeap.isEmpty()) { System.out.println("No element has been added, empty heap."); } } - // Get the element at a given index. The key for the list is equal to index value - 1 + /** + * Retrieves the element at the specified index without removing it. + * Note: The index is 1-based for consistency with heap operations. + * + * @param elementIndex 1-based index of the element to retrieve + * @return HeapElement at the specified index + * @throws IndexOutOfBoundsException if the index is invalid + */ public HeapElement getElement(int elementIndex) { if ((elementIndex <= 0) || (elementIndex > minHeap.size())) { - throw new IndexOutOfBoundsException("Index out of heap range"); + throw new IndexOutOfBoundsException("Index " + elementIndex + " is out of heap range [1, " + minHeap.size() + "]"); } return minHeap.get(elementIndex - 1); } - // Get the key of the element at a given index + /** + * Retrieves the key value of an element at the specified index. + * + * @param elementIndex 1-based index of the element + * @return double value representing the key + * @throws IndexOutOfBoundsException if the index is invalid + */ private double getElementKey(int elementIndex) { if ((elementIndex <= 0) || (elementIndex > minHeap.size())) { - throw new IndexOutOfBoundsException("Index out of heap range"); + throw new IndexOutOfBoundsException("Index " + elementIndex + " is out of heap range [1, " + minHeap.size() + "]"); } - return minHeap.get(elementIndex - 1).getKey(); } - // Swaps two elements in the heap + /** + * Swaps two elements in the heap. + * + * @param index1 1-based index of first element + * @param index2 1-based index of second element + */ private void swap(int index1, int index2) { HeapElement temporaryElement = minHeap.get(index1 - 1); minHeap.set(index1 - 1, minHeap.get(index2 - 1)); minHeap.set(index2 - 1, temporaryElement); } - // Toggle an element up to its right place as long as its key is lower than its parent's + /** + * Maintains heap properties by moving an element down the heap. + * Used specifically during initialization. + * + * @param elementIndex 1-based index of the element to heapify + */ + private void heapifyDown(int elementIndex) { + int smallest = elementIndex - 1; // Convert to 0-based index + int leftChild = 2 * elementIndex - 1; + int rightChild = 2 * elementIndex; + + // Check if left child is smaller than root + if (leftChild < minHeap.size() && minHeap.get(leftChild).getKey() < minHeap.get(smallest).getKey()) { + smallest = leftChild; + } + + // Check if right child is smaller than smallest so far + if (rightChild < minHeap.size() && minHeap.get(rightChild).getKey() < minHeap.get(smallest).getKey()) { + smallest = rightChild; + } + + // If smallest is not root + if (smallest != elementIndex - 1) { + HeapElement swap = minHeap.get(elementIndex - 1); + minHeap.set(elementIndex - 1, minHeap.get(smallest)); + minHeap.set(smallest, swap); + + // Recursively heapify the affected sub-tree + heapifyDown(smallest + 1); // Convert back to 1-based index + } + } + + /** + * Moves an element up the heap until heap properties are satisfied. + * This operation is called after insertion to maintain heap properties. + * + * @param elementIndex 1-based index of the element to move up + */ private void toggleUp(int elementIndex) { + if (elementIndex <= 1) { + return; + } + double key = minHeap.get(elementIndex - 1).getKey(); - while (getElementKey((int) Math.floor(elementIndex / 2.0) + 1) > key) { - swap(elementIndex, (int) Math.floor(elementIndex / 2.0)); - elementIndex = (int) Math.floor(elementIndex / 2.0); + int parentIndex = (int) Math.floor(elementIndex / 2.0); + + while (elementIndex > 1 && getElementKey(parentIndex) > key) { + swap(elementIndex, parentIndex); + elementIndex = parentIndex; + parentIndex = (int) Math.floor(elementIndex / 2.0); } } - // Toggle an element down to its right place as long as its key is higher - // than any of its children's + /** + * Moves an element down the heap until heap properties are satisfied. + * This operation is called after deletion to maintain heap properties. + * + * @param elementIndex 1-based index of the element to move down + */ private void toggleDown(int elementIndex) { double key = minHeap.get(elementIndex - 1).getKey(); - boolean wrongOrder = (key > getElementKey(elementIndex * 2)) || (key > getElementKey(Math.min(elementIndex * 2, minHeap.size()))); - while ((2 * elementIndex <= minHeap.size()) && wrongOrder) { - // Check whether it shall swap the element with its left child or its right one if any. - if ((2 * elementIndex < minHeap.size()) && (getElementKey(elementIndex * 2 + 1) < getElementKey(elementIndex * 2))) { - swap(elementIndex, 2 * elementIndex + 1); - elementIndex = 2 * elementIndex + 1; - } else { - swap(elementIndex, 2 * elementIndex); - elementIndex = 2 * elementIndex; + int size = minHeap.size(); + + while (true) { + int smallest = elementIndex; + int leftChild = 2 * elementIndex; + int rightChild = 2 * elementIndex + 1; + + if (leftChild <= size && getElementKey(leftChild) < key) { + smallest = leftChild; + } + + if (rightChild <= size && getElementKey(rightChild) < getElementKey(smallest)) { + smallest = rightChild; + } + + if (smallest == elementIndex) { + break; } - wrongOrder = (key > getElementKey(elementIndex * 2)) || (key > getElementKey(Math.min(elementIndex * 2, minHeap.size()))); + + swap(elementIndex, smallest); + elementIndex = smallest; } } - private HeapElement extractMin() { - HeapElement result = minHeap.get(0); - deleteElement(0); + /** + * Extracts and returns the minimum element from the heap. + * + * @return HeapElement with the lowest key + * @throws EmptyHeapException if the heap is empty + */ + private HeapElement extractMin() throws EmptyHeapException { + if (minHeap.isEmpty()) { + throw new EmptyHeapException("Cannot extract from empty heap"); + } + HeapElement result = minHeap.getFirst(); + deleteElement(1); return result; } + /** + * {@inheritDoc} + */ @Override - public final void insertElement(HeapElement element) { + public void insertElement(HeapElement element) { + if (element == null) { + throw new IllegalArgumentException("Cannot insert null element"); + } minHeap.add(element); toggleUp(minHeap.size()); } + /** + * {@inheritDoc} + */ @Override - public void deleteElement(int elementIndex) { + public void deleteElement(int elementIndex) throws EmptyHeapException { if (minHeap.isEmpty()) { - try { - throw new EmptyHeapException("Attempt to delete an element from an empty heap"); - } catch (EmptyHeapException e) { - e.printStackTrace(); - } + throw new EmptyHeapException("Cannot delete from empty heap"); } if ((elementIndex > minHeap.size()) || (elementIndex <= 0)) { - throw new IndexOutOfBoundsException("Index out of heap range"); - } - // The last element in heap replaces the one to be deleted - minHeap.set(elementIndex - 1, getElement(minHeap.size())); - minHeap.remove(minHeap.size()); - // Shall the new element be moved up... - if (getElementKey(elementIndex) < getElementKey((int) Math.floor(elementIndex / 2.0))) { - toggleUp(elementIndex); - } // ... or down ? - else if (((2 * elementIndex <= minHeap.size()) && (getElementKey(elementIndex) > getElementKey(elementIndex * 2))) || ((2 * elementIndex < minHeap.size()) && (getElementKey(elementIndex) > getElementKey(elementIndex * 2)))) { - toggleDown(elementIndex); + throw new IndexOutOfBoundsException("Index " + elementIndex + " is out of heap range [1, " + minHeap.size() + "]"); + } + + // Replace with last element and remove last position + minHeap.set(elementIndex - 1, minHeap.getLast()); + minHeap.removeLast(); + + // No need to toggle if we just removed the last element + if (!minHeap.isEmpty() && elementIndex <= minHeap.size()) { + // Determine whether to toggle up or down + if (elementIndex > 1 && getElementKey(elementIndex) < getElementKey((int) Math.floor(elementIndex / 2.0))) { + toggleUp(elementIndex); + } else { + toggleDown(elementIndex); + } } } + /** + * {@inheritDoc} + */ @Override public HeapElement getElement() throws EmptyHeapException { - try { - return extractMin(); - } catch (Exception e) { - throw new EmptyHeapException("Heap is empty. Error retrieving element", e); - } + return extractMin(); + } + + /** + * Returns the current size of the heap. + * + * @return number of elements in the heap + */ + public int size() { + return minHeap.size(); + } + + /** + * Checks if the heap is empty. + * + * @return true if the heap contains no elements + */ + public boolean isEmpty() { + return minHeap.isEmpty(); } } diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/MinPriorityQueue.java b/src/main/java/com/thealgorithms/datastructures/heaps/MinPriorityQueue.java index 9d19e9aaee1a..a1360b14dc5a 100644 --- a/src/main/java/com/thealgorithms/datastructures/heaps/MinPriorityQueue.java +++ b/src/main/java/com/thealgorithms/datastructures/heaps/MinPriorityQueue.java @@ -1,16 +1,23 @@ package com.thealgorithms.datastructures.heaps; /** - * Minimum Priority Queue It is a part of heap data structure A heap is a - * specific tree based data structure in which all the nodes of tree are in a - * specific order. that is the children are arranged in some respect of their - * parents, can either be greater or less than the parent. This makes it a min - * priority queue or max priority queue. + * A MinPriorityQueue is a specialized data structure that maintains the + * min-heap property, where the smallest element has the highest priority. * - *

+ *

In a min-priority queue, every parent node is less than or equal + * to its child nodes, which ensures that the smallest element can + * always be efficiently retrieved.

* - *

- * Functions: insert, delete, peek, isEmpty, print, heapSort, sink + *

Functions:

+ *
    + *
  • insert(int key): Inserts a new key into the queue.
  • + *
  • delete(): Removes and returns the highest priority value (the minimum).
  • + *
  • peek(): Returns the highest priority value without removing it.
  • + *
  • isEmpty(): Checks if the queue is empty.
  • + *
  • isFull(): Checks if the queue is full.
  • + *
  • heapSort(): Sorts the elements in ascending order.
  • + *
  • print(): Prints the current elements in the queue.
  • + *
*/ public class MinPriorityQueue { @@ -18,18 +25,25 @@ public class MinPriorityQueue { private final int capacity; private int size; - // class the constructor and initializes the capacity - MinPriorityQueue(int c) { + /** + * Initializes a new MinPriorityQueue with a specified capacity. + * + * @param c the maximum number of elements the queue can hold + */ + public MinPriorityQueue(int c) { this.capacity = c; this.size = 0; this.heap = new int[c + 1]; } - // inserts the key at the end and rearranges it - // so that the binary heap is in appropriate order + /** + * Inserts a new key into the min-priority queue. + * + * @param key the value to be inserted + */ public void insert(int key) { if (this.isFull()) { - return; + throw new IllegalStateException("MinPriorityQueue is full. Cannot insert new element."); } this.heap[this.size + 1] = key; int k = this.size + 1; @@ -44,89 +58,98 @@ public void insert(int key) { this.size++; } - // returns the highest priority value + /** + * Retrieves the highest priority value (the minimum) without removing it. + * + * @return the minimum value in the queue + * @throws IllegalStateException if the queue is empty + */ public int peek() { + if (isEmpty()) { + throw new IllegalStateException("MinPriorityQueue is empty. Cannot peek."); + } return this.heap[1]; } - // returns boolean value whether the heap is empty or not + /** + * Checks whether the queue is empty. + * + * @return true if the queue is empty, false otherwise + */ public boolean isEmpty() { - return 0 == this.size; + return size == 0; } - // returns boolean value whether the heap is full or not + /** + * Checks whether the queue is full. + * + * @return true if the queue is full, false otherwise + */ public boolean isFull() { - return this.size == this.capacity; + return size == capacity; } - // prints the heap + /** + * Prints the elements of the queue. + */ public void print() { - for (int i = 1; i <= this.capacity; i++) { + for (int i = 1; i <= this.size; i++) { System.out.print(this.heap[i] + " "); } System.out.println(); } - // heap sorting can be done by performing - // delete function to the number of times of the size of the heap - // it returns reverse sort because it is a min priority queue + /** + * Sorts the elements in the queue using heap sort. + */ public void heapSort() { - for (int i = 1; i < this.capacity; i++) { + for (int i = 1; i <= this.size; i++) { this.delete(); } } - // this function reorders the heap after every delete function + /** + * Reorders the heap after a deletion to maintain the heap property. + */ private void sink() { int k = 1; - while (2 * k <= this.size || 2 * k + 1 <= this.size) { - int minIndex; - if (this.heap[2 * k] >= this.heap[k]) { - if (2 * k + 1 <= this.size && this.heap[2 * k + 1] >= this.heap[k]) { - break; - } else if (2 * k + 1 > this.size) { - break; - } + while (2 * k <= this.size) { + int minIndex = k; // Assume current index is the minimum + + if (2 * k <= this.size && this.heap[2 * k] < this.heap[minIndex]) { + minIndex = 2 * k; // Left child is smaller } - if (2 * k + 1 > this.size) { - minIndex = this.heap[2 * k] < this.heap[k] ? 2 * k : k; - } else { - if (this.heap[k] > this.heap[2 * k] || this.heap[k] > this.heap[2 * k + 1]) { - minIndex = this.heap[2 * k] < this.heap[2 * k + 1] ? 2 * k : 2 * k + 1; - } else { - minIndex = k; - } + if (2 * k + 1 <= this.size && this.heap[2 * k + 1] < this.heap[minIndex]) { + minIndex = 2 * k + 1; // Right child is smaller } + + if (minIndex == k) { + break; // No swap needed, heap property is satisfied + } + + // Swap with the smallest child int temp = this.heap[k]; this.heap[k] = this.heap[minIndex]; this.heap[minIndex] = temp; - k = minIndex; + + k = minIndex; // Move down to the smallest child } } - // deletes the highest priority value from the heap + /** + * Deletes and returns the highest priority value (the minimum) from the queue. + * + * @return the minimum value from the queue + * @throws IllegalStateException if the queue is empty + */ public int delete() { + if (isEmpty()) { + throw new IllegalStateException("MinPriorityQueue is empty. Cannot delete."); + } int min = this.heap[1]; - this.heap[1] = this.heap[this.size]; - this.heap[this.size] = min; + this.heap[1] = this.heap[this.size]; // Move last element to the root this.size--; this.sink(); return min; } - - public static void main(String[] args) { - // testing - MinPriorityQueue q = new MinPriorityQueue(8); - q.insert(5); - q.insert(2); - q.insert(4); - q.insert(1); - q.insert(7); - q.insert(6); - q.insert(3); - q.insert(8); - q.print(); // [ 1, 2, 3, 5, 7, 6, 4, 8 ] - q.heapSort(); - q.print(); // [ 8, 7, 6, 5, 4, 3, 2, 1 ] - } } diff --git a/src/main/java/com/thealgorithms/datastructures/lists/CircleLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/CircleLinkedList.java index 2b50f73101fb..422e8953625f 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/CircleLinkedList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/CircleLinkedList.java @@ -1,8 +1,23 @@ package com.thealgorithms.datastructures.lists; +/** + * This class is a circular singly linked list implementation. In a circular linked list, + * the last node points back to the first node, creating a circular chain. + * + *

This implementation includes basic operations such as appending elements + * to the end, removing elements from a specified position, and converting + * the list to a string representation. + * + * @param the type of elements held in this list + */ public class CircleLinkedList { - private static final class Node { + /** + * A static nested class representing a node in the circular linked list. + * + * @param the type of element stored in the node + */ + static final class Node { Node next; E value; @@ -13,44 +28,56 @@ private Node(E value, Node next) { } } - // For better O.O design this should be private allows for better black box design private int size; - // this will point to dummy node; - private Node head = null; - private Node tail = null; // keeping a tail pointer to keep track of the end of list + Node head = null; + private Node tail; - // constructor for class.. here we will make a dummy node for circly linked list implementation - // with reduced error catching as our list will never be empty; + /** + * Initializes a new circular linked list. A dummy head node is used for simplicity, + * pointing initially to itself to ensure the list is never empty. + */ public CircleLinkedList() { - // creation of the dummy node - head = new Node(null, head); + head = new Node<>(null, head); tail = head; size = 0; } - // getter for the size... needed because size is private. + /** + * Returns the current size of the list. + * + * @return the number of elements in the list + */ public int getSize() { return size; } - // for the sake of simplistiy this class will only contain the append function or addLast other - // add functions can be implemented however this is the basses of them all really. + /** + * Appends a new element to the end of the list. Throws a NullPointerException if + * a null value is provided. + * + * @param value the value to append to the list + * @throws NullPointerException if the value is null + */ public void append(E value) { if (value == null) { - // we do not want to add null elements to the list. throw new NullPointerException("Cannot add null element to the list"); } - // head.next points to the last element; if (tail == null) { - tail = new Node(value, head); + tail = new Node<>(value, head); head.next = tail; } else { - tail.next = new Node(value, head); + tail.next = new Node<>(value, head); tail = tail.next; } size++; } + /** + * Returns a string representation of the list in the format "[ element1, element2, ... ]". + * An empty list is represented as "[]". + * + * @return the string representation of the list + */ public String toString() { if (size == 0) { return "[]"; @@ -68,23 +95,27 @@ public String toString() { return sb.toString(); } + /** + * Removes and returns the element at the specified position in the list. + * Throws an IndexOutOfBoundsException if the position is invalid. + * + * @param pos the position of the element to remove + * @return the value of the removed element + * @throws IndexOutOfBoundsException if the position is out of range + */ public E remove(int pos) { if (pos >= size || pos < 0) { - // catching errors - throw new IndexOutOfBoundsException("position cannot be greater than size or negative"); + throw new IndexOutOfBoundsException("Position out of bounds"); } - // we need to keep track of the element before the element we want to remove we can see why - // bellow. + Node before = head; for (int i = 1; i <= pos; i++) { before = before.next; } Node destroy = before.next; E saved = destroy.value; - // assigning the next reference to the element following the element we want to remove... - // the last element will be assigned to the head. - before.next = before.next.next; - // scrubbing + before.next = destroy.next; + if (destroy == tail) { tail = before; } diff --git a/src/main/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursion.java b/src/main/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursion.java index 8d864bc8caae..b58d51e7e5fe 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursion.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursion.java @@ -1,27 +1,26 @@ package com.thealgorithms.datastructures.lists; +/** + * CountSinglyLinkedListRecursion extends a singly linked list to include a + * recursive count method, which calculates the number of nodes in the list. + */ public class CountSinglyLinkedListRecursion extends SinglyLinkedList { - public static void main(String[] args) { - CountSinglyLinkedListRecursion list = new CountSinglyLinkedListRecursion(); - for (int i = 1; i <= 5; ++i) { - list.insert(i); - } - assert list.count() == 5; - } - /** - * Calculate the count of the list manually using recursion. + * Recursively calculates the number of nodes in the list. * - * @param head head of the list. - * @return count of the list. + * @param head the head node of the list segment being counted. + * @return the count of nodes from the given head node onward. */ - private int countRecursion(Node head) { + private int countRecursion(SinglyLinkedListNode head) { return head == null ? 0 : 1 + countRecursion(head.next); } /** - * Returns the count of the list. + * Returns the total number of nodes in the list by invoking the recursive + * count helper method. + * + * @return the total node count in the list. */ @Override public int count() { diff --git a/src/main/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoop.java b/src/main/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoop.java index 441c95702050..3902d08bfd14 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoop.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoop.java @@ -1,64 +1,80 @@ package com.thealgorithms.datastructures.lists; -import java.util.Scanner; - +/** + * CreateAndDetectLoop provides utility methods for creating and detecting loops + * (cycles) in a singly linked list. Loops in a linked list are created by + * connecting the "next" pointer of one node to a previous node in the list, + * forming a cycle. + */ public final class CreateAndDetectLoop { + + /** + * Private constructor to prevent instantiation of this utility class. + */ private CreateAndDetectLoop() { + throw new UnsupportedOperationException("Utility class"); } /** - * Prints the linked list. - * - * @param head head node of the linked list + * Node represents an individual element in the linked list, containing + * data and a reference to the next node. */ - static void printList(Node head) { - Node cur = head; + static final class Node { + int data; + Node next; - while (cur != null) { - System.out.print(cur.value + " "); - cur = cur.next; + Node(int data) { + this.data = data; + next = null; } } /** - * Creates a loop in the linked list. + * Creates a loop in a linked list by connecting the next pointer of a node + * at a specified starting position (position2) to another node at a specified + * destination position (position1). If either position is invalid, no loop + * will be created. * - * @see - * - * GeeksForGeeks: Make a loop at K-th position - * @param head head node of the linked list - * @param k position of node where loop is to be created + * @param head the head node of the linked list + * @param position1 the position in the list where the loop should end + * @param position2 the position in the list where the loop should start */ - static void createLoop(Node head, int k) { - if (head == null) { + static void createLoop(Node head, int position1, int position2) { + if (position1 == 0 || position2 == 0) { return; } - Node temp = head; - int count = 1; - while (count < k) { // Traverse the list till the kth node - temp = temp.next; - count++; - } - Node connectedPoint = temp; + Node node1 = head; + Node node2 = head; + + int count1 = 1; + int count2 = 1; + // Traverse to the node at position1 + while (count1 < position1 && node1 != null) { + node1 = node1.next; + count1++; + } - while (temp.next != null) { // Traverse remaining nodes - temp = temp.next; + // Traverse to the node at position2 + while (count2 < position2 && node2 != null) { + node2 = node2.next; + count2++; } - temp.next = connectedPoint; // Connect last node to k-th element + // If both nodes are valid, create the loop + if (node1 != null && node2 != null) { + node2.next = node1; + } } /** - * Detects the presence of a loop in the linked list. - * - * @see - * - * Floyd's Cycle Detection Algorithm + * Detects the presence of a loop in the linked list using Floyd's cycle-finding + * algorithm, also known as the "tortoise and hare" method. * * @param head the head node of the linked list - * - * @return true if loop exists else false + * @return true if a loop is detected, false otherwise + * @see + * Floyd's Cycle Detection Algorithm */ static boolean detectLoop(Node head) { Node sptr = head; @@ -67,40 +83,10 @@ static boolean detectLoop(Node head) { while (fptr != null && fptr.next != null) { sptr = sptr.next; fptr = fptr.next.next; - if (fptr == sptr) { + if (sptr == fptr) { return true; } } - return false; } - - public static void main(String[] args) { - SinglyLinkedList singlyLinkedList = new SinglyLinkedList(); - Scanner sc = new Scanner(System.in); - - System.out.println("Enter the number of elements to be inserted: "); - int n = sc.nextInt(); - System.out.printf("Enter the %d elements: %n", n); - while (n-- > 0) { - singlyLinkedList.insert(sc.nextInt()); - } - - System.out.print("Given list: "); - printList(singlyLinkedList.getHead()); - System.out.println(); - - System.out.println("Enter the location to generate loop: "); - int k = sc.nextInt(); - - createLoop(singlyLinkedList.getHead(), k); - - if (detectLoop(singlyLinkedList.getHead())) { - System.out.println("Loop found"); - } else { - System.out.println("No loop found"); - } - - sc.close(); - } } diff --git a/src/main/java/com/thealgorithms/datastructures/lists/CursorLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/CursorLinkedList.java index b4fa9c51d4dc..ff3d39115c3b 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/CursorLinkedList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/CursorLinkedList.java @@ -3,18 +3,20 @@ import java.util.Objects; /** - * This class implements a Cursor Linked List. - * - * A CursorLinkedList is an array version of a Linked List. Essentially you have - * an array of list nodes but instead of each node containing a pointer to the - * next item in the linked list, each node element in the array contains the - * index for the next node element. + * CursorLinkedList is an array-based implementation of a singly linked list. + * Each node in the array simulates a linked list node, storing an element and + * the index of the next node. This structure allows for efficient list operations + * without relying on traditional pointers. * + * @param the type of elements in this list */ public class CursorLinkedList { + /** + * Node represents an individual element in the list, containing the element + * itself and a pointer (index) to the next node. + */ private static class Node { - T element; int next; @@ -31,7 +33,7 @@ private static class Node { private static final int CURSOR_SPACE_SIZE = 100; { - // init at loading time + // Initialize cursor space array and free list pointers cursorSpace = new Node[CURSOR_SPACE_SIZE]; for (int i = 0; i < CURSOR_SPACE_SIZE; i++) { cursorSpace[i] = new Node<>(null, i + 1); @@ -39,12 +41,18 @@ private static class Node { cursorSpace[CURSOR_SPACE_SIZE - 1].next = 0; } + /** + * Constructs an empty CursorLinkedList with the default capacity. + */ public CursorLinkedList() { os = 0; count = 0; head = -1; } + /** + * Prints all elements in the list in their current order. + */ public void printList() { if (head != -1) { int start = head; @@ -57,27 +65,36 @@ public void printList() { } /** - * @return the logical index of the element within the list , not the actual - * index of the [cursorSpace] array + * Finds the logical index of a specified element in the list. + * + * @param element the element to search for in the list + * @return the logical index of the element, or -1 if not found + * @throws NullPointerException if element is null */ public int indexOf(T element) { - Objects.requireNonNull(element); - Node iterator = cursorSpace[head]; - for (int i = 0; i < count; i++) { - if (iterator.element.equals(element)) { - return i; + if (element == null) { + throw new NullPointerException("Element cannot be null"); + } + try { + Objects.requireNonNull(element); + Node iterator = cursorSpace[head]; + for (int i = 0; i < count; i++) { + if (iterator.element.equals(element)) { + return i; + } + iterator = cursorSpace[iterator.next]; } - iterator = cursorSpace[iterator.next]; + } catch (Exception e) { + return -1; } - return -1; } /** - * @param position , the logical index of the element , not the actual one - * within the [cursorSpace] array . this method should be used to get the - * index give by indexOf() method. - * @return + * Retrieves an element at a specified logical index in the list. + * + * @param position the logical index of the element + * @return the element at the specified position, or null if index is out of bounds */ public T get(int position) { if (position >= 0 && position < count) { @@ -88,15 +105,18 @@ public T get(int position) { if (counter == position) { return element; } - start = cursorSpace[start].next; counter++; } } - return null; } + /** + * Removes the element at a specified logical index from the list. + * + * @param index the logical index of the element to remove + */ public void removeByIndex(int index) { if (index >= 0 && index < count) { T element = get(index); @@ -104,19 +124,22 @@ public void removeByIndex(int index) { } } + /** + * Removes a specified element from the list. + * + * @param element the element to be removed + * @throws NullPointerException if element is null + */ public void remove(T element) { Objects.requireNonNull(element); - - // case element is in the head T tempElement = cursorSpace[head].element; int tempNext = cursorSpace[head].next; if (tempElement.equals(element)) { free(head); head = tempNext; - } else { // otherwise cases + } else { int prevIndex = head; int currentIndex = cursorSpace[prevIndex].next; - while (currentIndex != -1) { T currentElement = cursorSpace[currentIndex].element; if (currentElement.equals(element)) { @@ -124,15 +147,34 @@ public void remove(T element) { free(currentIndex); break; } - prevIndex = currentIndex; currentIndex = cursorSpace[prevIndex].next; } } - count--; } + /** + * Allocates a new node index for storing an element. + * + * @return the index of the newly allocated node + * @throws OutOfMemoryError if no space is available in cursor space + */ + private int alloc() { + int availableNodeIndex = cursorSpace[os].next; + if (availableNodeIndex == 0) { + throw new OutOfMemoryError(); + } + cursorSpace[os].next = cursorSpace[availableNodeIndex].next; + cursorSpace[availableNodeIndex].next = -1; + return availableNodeIndex; + } + + /** + * Releases a node back to the free list. + * + * @param index the index of the node to release + */ private void free(int index) { Node osNode = cursorSpace[os]; int osNext = osNode.next; @@ -141,44 +183,26 @@ private void free(int index) { cursorSpace[index].next = osNext; } + /** + * Appends an element to the end of the list. + * + * @param element the element to append + * @throws NullPointerException if element is null + */ public void append(T element) { Objects.requireNonNull(element); int availableIndex = alloc(); cursorSpace[availableIndex].element = element; - if (head == -1) { head = availableIndex; + } else { + int iterator = head; + while (cursorSpace[iterator].next != -1) { + iterator = cursorSpace[iterator].next; + } + cursorSpace[iterator].next = availableIndex; } - - int iterator = head; - while (cursorSpace[iterator].next != -1) { - iterator = cursorSpace[iterator].next; - } - - cursorSpace[iterator].next = availableIndex; cursorSpace[availableIndex].next = -1; - count++; } - - /** - * @return the index of the next available node - */ - private int alloc() { - // 1- get the index at which the os is pointing - int availableNodeIndex = cursorSpace[os].next; - - if (availableNodeIndex == 0) { - throw new OutOfMemoryError(); - } - - // 2- make the os point to the next of the @var{availableNodeIndex} - cursorSpace[os].next = cursorSpace[availableNodeIndex].next; - - // this to indicate an end of the list , helpful at testing since any err - // would throw an outOfBoundException - cursorSpace[availableNodeIndex].next = -1; - - return availableNodeIndex; - } } diff --git a/src/main/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedList.java index 0eac20d2e9ad..0f5c50530d92 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedList.java @@ -1,51 +1,94 @@ package com.thealgorithms.datastructures.lists; -import java.util.Arrays; import java.util.Comparator; import java.util.PriorityQueue; /** + * The MergeKSortedLinkedList class provides a method to merge multiple sorted linked lists + * into a single sorted linked list. + * This implementation uses a min-heap (priority queue) to efficiently + * find the smallest node across all lists, thus optimizing the merge process. + * + *

Example usage: + *

+ * Node list1 = new Node(1, new Node(4, new Node(5)));
+ * Node list2 = new Node(1, new Node(3, new Node(4)));
+ * Node list3 = new Node(2, new Node(6));
+ * Node[] lists = { list1, list2, list3 };
+ *
+ * MergeKSortedLinkedList merger = new MergeKSortedLinkedList();
+ * Node mergedHead = merger.mergeKList(lists, lists.length);
+ * 
+ *

+ * + *

This class is designed to handle nodes of integer linked lists and can be expanded for additional data types if needed.

+ * * @author Arun Pandey (https://github.com/pandeyarun709) */ public class MergeKSortedLinkedList { /** - * This function merge K sorted LinkedList + * Merges K sorted linked lists into a single sorted linked list. * - * @param a array of LinkedList - * @param n size of array - * @return node + *

This method uses a priority queue (min-heap) to repeatedly extract the smallest node from the heads of all the lists, + * then inserts the next node from that list into the heap. The process continues until all nodes have been processed, + * resulting in a fully merged and sorted linked list.

+ * + * @param a Array of linked list heads to be merged. + * @param n Number of linked lists. + * @return Head of the merged sorted linked list. */ Node mergeKList(Node[] a, int n) { - // Min Heap - PriorityQueue min = new PriorityQueue<>(Comparator.comparingInt(x -> x.data)); + if (a == null || n == 0) { + return null; + } - // adding head of all linkedList in min heap - min.addAll(Arrays.asList(a).subList(0, n)); + // Min Heap to store nodes based on their values for efficient retrieval of the smallest element. + PriorityQueue minHeap = new PriorityQueue<>(Comparator.comparingInt(x -> x.data)); - // Make new head among smallest heads in K linkedList - Node head = min.poll(); - min.add(head.next); - Node curr = head; + // Initialize the min-heap with the head of each non-null linked list + for (Node node : a) { + if (node != null) { + minHeap.add(node); + } + } + + // Start merging process + Node head = minHeap.poll(); // Smallest head is the initial head of the merged list + if (head != null && head.next != null) { + minHeap.add(head.next); + } - // merging LinkedList - while (!min.isEmpty()) { - Node temp = min.poll(); + Node curr = head; + while (!minHeap.isEmpty()) { + Node temp = minHeap.poll(); curr.next = temp; curr = temp; - // Add Node in min Heap only if temp.next is not null + // Add the next node in the current list to the heap if it exists if (temp.next != null) { - min.add(temp.next); + minHeap.add(temp.next); } } return head; } - private final class Node { + /** + * Represents a node in the linked list. + */ + static class Node { + int data; + Node next; + + Node(int data) { + this.data = data; + this.next = null; + } - private int data; - private Node next; + Node(int data, Node next) { + this.data = data; + this.next = next; + } } } diff --git a/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedArrayList.java b/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedArrayList.java index 99ab09f81c1c..09eb854c8dc2 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedArrayList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedArrayList.java @@ -1,48 +1,55 @@ package com.thealgorithms.datastructures.lists; -import java.util.ArrayList; +import java.util.Collection; import java.util.List; /** + * Utility class for merging two sorted ArrayLists of integers into a single sorted collection. + * + *

This class provides a static `merge` method to combine two pre-sorted lists of integers into a + * single sorted list. It does so without modifying the input lists by adding elements from both lists in sorted order + * into the result list.

+ * + *

Example usage:

+ *
+ * List listA = Arrays.asList(1, 3, 5, 7, 9);
+ * List listB = Arrays.asList(2, 4, 6, 8, 10);
+ * List result = new ArrayList<>();
+ * MergeSortedArrayList.merge(listA, listB, result);
+ * 
+ * + *

The resulting `result` list will be [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].

+ * + *

Note: This class cannot be instantiated as it is designed to be used only with its static `merge` method.

+ * + *

This implementation assumes the input lists are already sorted in ascending order.

+ * * @author https://github.com/shellhub + * @see List */ public final class MergeSortedArrayList { - private MergeSortedArrayList() { - } - - public static void main(String[] args) { - List listA = new ArrayList<>(); - List listB = new ArrayList<>(); - List listC = new ArrayList<>(); - - /* init ListA and List B */ - for (int i = 1; i <= 10; i += 2) { - listA.add(i); - /* listA: [1, 3, 5, 7, 9] */ - listB.add(i + 1); - /* listB: [2, 4, 6, 8, 10] */ - } - - /* merge listA and listB to listC */ - merge(listA, listB, listC); - System.out.println("listA: " + listA); - System.out.println("listB: " + listB); - System.out.println("listC: " + listC); + private MergeSortedArrayList() { } /** - * merge two sorted ArrayList + * Merges two sorted lists of integers into a single sorted collection. + * + *

This method does not alter the original lists (`listA` and `listB`). Instead, it inserts elements from both + * lists into `listC` in a way that maintains ascending order.

* - * @param listA the first list to merge - * @param listB the second list to merge - * @param listC the result list after merging + * @param listA The first sorted list of integers. + * @param listB The second sorted list of integers. + * @param listC The collection to hold the merged result, maintaining sorted order. + * @throws NullPointerException if any of the input lists or result collection is null. */ - public static void merge(List listA, List listB, List listC) { + public static void merge(List listA, List listB, Collection listC) { + if (listA == null || listB == null || listC == null) { + throw new NullPointerException("Input lists and result collection must not be null."); + } + int pa = 0; - /* the index of listA */ int pb = 0; - /* the index of listB */ while (pa < listA.size() && pb < listB.size()) { if (listA.get(pa) <= listB.get(pb)) { @@ -52,12 +59,11 @@ public static void merge(List listA, List listB, List } } - /* copy left element of listA to listC */ + // Add remaining elements from listA, if any while (pa < listA.size()) { listC.add(listA.get(pa++)); } - - /* copy left element of listB to listC */ + // Add remaining elements from listB, if any while (pb < listB.size()) { listC.add(listB.get(pb++)); } diff --git a/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedList.java index 2bee945c9db6..4e99642fccd8 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedList.java @@ -1,35 +1,53 @@ package com.thealgorithms.datastructures.lists; +/** + * Utility class for merging two sorted singly linked lists. + * + *

This class extends the {@link SinglyLinkedList} class to support the merging of two sorted linked lists. + * It provides a static method, `merge`, that takes two sorted singly linked lists, merges them into a single sorted linked list, + * and returns the result.

+ * + *

Example usage:

+ *
+ * SinglyLinkedList listA = new SinglyLinkedList();
+ * SinglyLinkedList listB = new SinglyLinkedList();
+ * for (int i = 2; i <= 10; i += 2) {
+ *     listA.insert(i);   // listA: 2->4->6->8->10
+ *     listB.insert(i - 1); // listB: 1->3->5->7->9
+ * }
+ * SinglyLinkedList mergedList = MergeSortedSinglyLinkedList.merge(listA, listB);
+ * System.out.println(mergedList); // Output: 1->2->3->4->5->6->7->8->9->10
+ * 
+ * + *

The `merge` method assumes that both input lists are already sorted in ascending order. + * It returns a new singly linked list that contains all elements from both lists in sorted order.

+ * + * @see SinglyLinkedList + */ public class MergeSortedSinglyLinkedList extends SinglyLinkedList { - public static void main(String[] args) { - SinglyLinkedList listA = new SinglyLinkedList(); - SinglyLinkedList listB = new SinglyLinkedList(); - - for (int i = 2; i <= 10; i += 2) { - listA.insert(i); - listB.insert(i - 1); - } - assert listA.toString().equals("2->4->6->8->10"); - assert listB.toString().equals("1->3->5->7->9"); - assert merge(listA, listB).toString().equals("1->2->3->4->5->6->7->8->9->10"); - } - /** - * Merge two sorted SingleLinkedList + * Merges two sorted singly linked lists into a single sorted singly linked list. + * + *

This method does not modify the input lists; instead, it creates a new merged linked list + * containing all elements from both lists in sorted order.

* - * @param listA the first sorted list - * @param listB the second sored list - * @return merged sorted list + * @param listA The first sorted singly linked list. + * @param listB The second sorted singly linked list. + * @return A new singly linked list containing all elements from both lists in sorted order. + * @throws NullPointerException if either input list is null. */ public static SinglyLinkedList merge(SinglyLinkedList listA, SinglyLinkedList listB) { - Node headA = listA.getHead(); - Node headB = listB.getHead(); + if (listA == null || listB == null) { + throw new NullPointerException("Input lists must not be null."); + } + SinglyLinkedListNode headA = listA.getHead(); + SinglyLinkedListNode headB = listB.getHead(); int size = listA.size() + listB.size(); - Node head = new Node(); - Node tail = head; + SinglyLinkedListNode head = new SinglyLinkedListNode(); + SinglyLinkedListNode tail = head; while (headA != null && headB != null) { if (headA.value <= headB.value) { tail.next = headA; @@ -40,12 +58,10 @@ public static SinglyLinkedList merge(SinglyLinkedList listA, SinglyLinkedList li } tail = tail.next; } - if (headA == null) { - tail.next = headB; - } - if (headB == null) { - tail.next = headA; - } + + // Attach remaining nodes + tail.next = (headA == null) ? headB : headA; + return new SinglyLinkedList(head.next, size); } } diff --git a/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java index 36b6e9c62cbe..f018781ada70 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java @@ -104,36 +104,52 @@ public class QuickSortLinkedList { - private SinglyLinkedList list = null; // Linked list - private Node head = null; // head of the list - // Counstructor + private final SinglyLinkedList list; // The linked list to be sorted + private SinglyLinkedListNode head; // Head of the list + + /** + * Constructor that initializes the QuickSortLinkedList with a given linked list. + * + * @param list The singly linked list to be sorted + */ public QuickSortLinkedList(SinglyLinkedList list) { this.list = list; this.head = list.getHead(); } - // Function to sort a linked list using the Quick Sort algorithm + /** + * Sorts the linked list using the QuickSort algorithm. + * The sorted list replaces the original list within the SinglyLinkedList instance. + */ public void sortList() { head = sortList(head); list.setHead(head); } - // helper function to apply QuickSort to the stored list - public Node sortList(Node head) { + + /** + * Recursively sorts a linked list by partitioning it around a pivot element. + * + *

Each recursive call selects a pivot, partitions the list into elements less + * than the pivot and elements greater than or equal to the pivot, then combines + * the sorted sublists around the pivot.

+ * + * @param head The head node of the list to sort + * @return The head node of the sorted linked list + */ + private SinglyLinkedListNode sortList(SinglyLinkedListNode head) { if (head == null || head.next == null) { return head; } - // Choose the first element as the pivot - Node pivot = head; + SinglyLinkedListNode pivot = head; head = head.next; pivot.next = null; - Node lessHead = new Node(); // stores the nodes cantaining data less than pivot node - Node lessTail = lessHead; // tail of lessHead - Node greaterHead = new Node(); // stores the nodes cantaining data greater than pivot node - Node greaterTail = greaterHead; // tail of greaterHead + SinglyLinkedListNode lessHead = new SinglyLinkedListNode(); + SinglyLinkedListNode lessTail = lessHead; + SinglyLinkedListNode greaterHead = new SinglyLinkedListNode(); + SinglyLinkedListNode greaterTail = greaterHead; - // Partition the list around the pivot while (head != null) { if (head.value < pivot.value) { lessTail.next = head; @@ -145,20 +161,17 @@ public Node sortList(Node head) { head = head.next; } - // Seperating lessHead and greaterHead to form two seperate linkedList lessTail.next = null; greaterTail.next = null; - // Recursively sort the sublists - Node sortedLess = sortList(lessHead.next); - Node sortedGreater = sortList(greaterHead.next); + SinglyLinkedListNode sortedLess = sortList(lessHead.next); + SinglyLinkedListNode sortedGreater = sortList(greaterHead.next); - // Combine the sorted sublists and pivot if (sortedLess == null) { pivot.next = sortedGreater; return pivot; } else { - Node current = sortedLess; + SinglyLinkedListNode current = sortedLess; while (current.next != null) { current = current.next; } diff --git a/src/main/java/com/thealgorithms/datastructures/lists/README.md b/src/main/java/com/thealgorithms/datastructures/lists/README.md index ea389c0422ce..6aefa4c98e6d 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/README.md +++ b/src/main/java/com/thealgorithms/datastructures/lists/README.md @@ -1,7 +1,7 @@ ## Linked List ### Description -LinkedList is a data structure in which data is stored in a linear manner. It usually contains a data field and a link to the memory location of the next mode. +LinkedList is a data structure in which data is stored in a linear manner. It usually contains a data field and a link to the memory location of the next node. ### Structure diff --git a/src/main/java/com/thealgorithms/datastructures/lists/ReverseKGroup.java b/src/main/java/com/thealgorithms/datastructures/lists/ReverseKGroup.java index 3c4b9331266c..9b9464d388b5 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/ReverseKGroup.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/ReverseKGroup.java @@ -1,13 +1,45 @@ package com.thealgorithms.datastructures.lists; /** - * Reverse K Group LinkedList (https://www.topcoder.com/thrive/articles/reverse-node-in-k-group) + * The ReverseKGroup class provides functionality to reverse nodes in a + * linked list in groups of k nodes. + *

+ * This algorithm follows the approach of reversing the linked list in segments of + * size k. If the remaining nodes are fewer than k, they remain unchanged. + *

+ *

+ * Example: + * Given a linked list: 1 -> 2 -> 3 -> 4 -> 5 and k = 3, + * the output will be: 3 -> 2 -> 1 -> 4 -> 5. + *

+ *

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

+ *

+ * Complexity: + *

    + *
  • Time Complexity: O(n), where n is the number of nodes in the linked list.
  • + *
  • Space Complexity: O(1), as we are reversing in place.
  • + *
+ *

+ * * Author: Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi) */ - public class ReverseKGroup { - public int length(Node head) { - Node curr = head; + + /** + * Calculates the length of the linked list. + * + * @param head The head node of the linked list. + * @return The total number of nodes in the linked list. + */ + public int length(SinglyLinkedListNode head) { + SinglyLinkedListNode curr = head; int count = 0; while (curr != null) { curr = curr.next; @@ -15,15 +47,23 @@ public int length(Node head) { } return count; } - // reverse function - public Node reverse(Node head, int count, int k) { + + /** + * Reverses the linked list in groups of k nodes. + * + * @param head The head node of the linked list. + * @param count The remaining number of nodes. + * @param k The size of the group to reverse. + * @return The new head of the reversed linked list segment. + */ + public SinglyLinkedListNode reverse(SinglyLinkedListNode head, int count, int k) { if (count < k) { return head; } - Node prev = null; + SinglyLinkedListNode prev = null; int count1 = 0; - Node curr = head; - Node next = null; + SinglyLinkedListNode curr = head; + SinglyLinkedListNode next = null; while (curr != null && count1 < k) { next = curr.next; curr.next = prev; @@ -37,9 +77,16 @@ public Node reverse(Node head, int count, int k) { } return prev; } - public Node reverseKGroup(Node head, int k) { + + /** + * Reverses the linked list in groups of k nodes. + * + * @param head The head node of the linked list. + * @param k The size of the group to reverse. + * @return The head of the modified linked list after reversal. + */ + public SinglyLinkedListNode reverseKGroup(SinglyLinkedListNode head, int k) { int count = length(head); - Node ans = reverse(head, count, k); - return ans; + return reverse(head, count, k); } } diff --git a/src/main/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedLists.java b/src/main/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedLists.java index e7ea95d3f037..47ee5397097c 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedLists.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedLists.java @@ -1,17 +1,49 @@ package com.thealgorithms.datastructures.lists; /** - * Rotate a list - * @author Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi) + * The RotateSinglyLinkedLists class provides a method to rotate a singly linked list + * to the right by a specified number of positions. + *

+ * In a right rotation by `k` steps, each node in the list moves `k` positions to the right. + * Nodes that are rotated off the end of the list are placed back at the beginning. + *

+ *

+ * Example: + * Given linked list: 1 -> 2 -> 3 -> 4 -> 5 and k = 2, the output will be: + * 4 -> 5 -> 1 -> 2 -> 3. + *

+ *

+ * Edge Cases: + *

    + *
  • If the list is empty, returns null.
  • + *
  • If `k` is 0 or a multiple of the list length, the list remains unchanged.
  • + *
+ *

+ *

+ * Complexity: + *

    + *
  • Time Complexity: O(n), where n is the number of nodes in the linked list.
  • + *
  • Space Complexity: O(1), as we only use a constant amount of additional space.
  • + *
+ *

+ * + * Author: Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi) */ - public class RotateSinglyLinkedLists { - public Node rotateRight(Node head, int k) { + + /** + * Rotates a singly linked list to the right by `k` positions. + * + * @param head The head node of the singly linked list. + * @param k The number of positions to rotate the list to the right. + * @return The head of the rotated linked list. + */ + public SinglyLinkedListNode rotateRight(SinglyLinkedListNode head, int k) { if (head == null || head.next == null || k == 0) { return head; } - Node curr = head; + SinglyLinkedListNode curr = head; int len = 1; while (curr.next != null) { curr = curr.next; diff --git a/src/main/java/com/thealgorithms/datastructures/lists/SearchSinglyLinkedListRecursion.java b/src/main/java/com/thealgorithms/datastructures/lists/SearchSinglyLinkedListRecursion.java index 35f8c9a95b56..4ac2de422595 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/SearchSinglyLinkedListRecursion.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/SearchSinglyLinkedListRecursion.java @@ -1,31 +1,45 @@ package com.thealgorithms.datastructures.lists; +/** + * The SearchSinglyLinkedListRecursion class extends SinglyLinkedList and provides + * a method to search for a value in a singly linked list using recursion. + *

+ * This class demonstrates a recursive approach to check if a given integer value is + * present in the linked list. The search method calls a private recursive helper method + * `searchRecursion`, which checks each node's value and moves to the next node if necessary. + *

+ *

+ * Example: + * Given a list containing the values 1 -> 2 -> 3 -> 4, calling search(3) will return `true`, + * while calling search(5) will return `false`. + *

+ *

+ * Complexity: + *

    + *
  • Time Complexity: O(n), where n is the number of nodes in the linked list.
  • + *
  • Space Complexity: O(n), due to the recursive call stack in the worst case.
  • + *
+ *

+ */ public class SearchSinglyLinkedListRecursion extends SinglyLinkedList { - public static void main(String[] args) { - SearchSinglyLinkedListRecursion list = new SearchSinglyLinkedListRecursion(); - for (int i = 1; i <= 10; ++i) { - list.insert(i); - } - - for (int i = 1; i <= 10; ++i) { - assert list.search(i); - } - assert !list.search(-1) && !list.search(100); - } - /** - * Test if the value key is present in the list using recursion. + * Recursively searches for a given value in the linked list. * - * @param node the head node. - * @param key the value to be searched. - * @return {@code true} if key is present in the list, otherwise - * {@code false}. + * @param node the head node to start the search. + * @param key the integer value to be searched for. + * @return {@code true} if the value `key` is present in the list; otherwise, {@code false}. */ - private boolean searchRecursion(Node node, int key) { + private boolean searchRecursion(SinglyLinkedListNode node, int key) { return (node != null && (node.value == key || searchRecursion(node.next, key))); } + /** + * Public search method to determine if a key is present in the linked list. + * + * @param key the integer value to be searched for. + * @return {@code true} if the value `key` is present in the list; otherwise, {@code false}. + */ @Override public boolean search(int key) { return searchRecursion(getHead(), key); diff --git a/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedList.java index d5d3f31f4b66..ff4af4437cc7 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedList.java @@ -12,7 +12,7 @@ public class SinglyLinkedList implements Iterable { /** * Head refer to the front of the list */ - private Node head; + private SinglyLinkedListNode head; /** * Size of SinglyLinkedList @@ -33,7 +33,7 @@ public SinglyLinkedList() { * @param head the head node of list * @param size the size of list */ - public SinglyLinkedList(Node head, int size) { + public SinglyLinkedList(SinglyLinkedListNode head, int size) { this.head = head; this.size = size; } @@ -44,8 +44,8 @@ public SinglyLinkedList(Node head, int size) { * */ public boolean detectLoop() { - Node currentNodeFast = head; - Node currentNodeSlow = head; + SinglyLinkedListNode currentNodeFast = head; + SinglyLinkedListNode currentNodeSlow = head; while (currentNodeFast != null && currentNodeFast.next != null) { currentNodeFast = currentNodeFast.next.next; currentNodeSlow = currentNodeSlow.next; @@ -61,12 +61,12 @@ public boolean detectLoop() { * If the length of the list is even then return item number length/2 * @return middle node of the list */ - public Node middle() { + public SinglyLinkedListNode middle() { if (head == null) { return null; } - Node firstCounter = head; - Node secondCounter = firstCounter.next; + SinglyLinkedListNode firstCounter = head; + SinglyLinkedListNode secondCounter = firstCounter.next; while (secondCounter != null && secondCounter.next != null) { firstCounter = firstCounter.next; secondCounter = secondCounter.next.next; @@ -82,15 +82,15 @@ public void swapNodes(int valueFirst, int valueSecond) { if (valueFirst == valueSecond) { return; } - Node previousA = null; - Node currentA = head; + SinglyLinkedListNode previousA = null; + SinglyLinkedListNode currentA = head; while (currentA != null && currentA.value != valueFirst) { previousA = currentA; currentA = currentA.next; } - Node previousB = null; - Node currentB = head; + SinglyLinkedListNode previousB = null; + SinglyLinkedListNode currentB = head; while (currentB != null && currentB.value != valueSecond) { previousB = currentB; currentB = currentB.next; @@ -117,7 +117,7 @@ public void swapNodes(int valueFirst, int valueSecond) { } // Swap next pointer - Node temp = currentA.next; + var temp = currentA.next; currentA.next = currentB.next; currentB.next = temp; } @@ -126,12 +126,12 @@ public void swapNodes(int valueFirst, int valueSecond) { * Reverse a singly linked list[Iterative] from a given node till the end * */ - public Node reverseListIter(Node node) { - Node prev = null; - Node curr = node; + public SinglyLinkedListNode reverseListIter(SinglyLinkedListNode node) { + SinglyLinkedListNode prev = null; + SinglyLinkedListNode curr = node; while (curr != null && curr.next != null) { - Node next = curr.next; + var next = curr.next; curr.next = prev; prev = curr; curr = next; @@ -149,13 +149,13 @@ public Node reverseListIter(Node node) { * Reverse a singly linked list[Recursive] from a given node till the end * */ - public Node reverseListRec(Node head) { + public SinglyLinkedListNode reverseListRec(SinglyLinkedListNode head) { if (head == null || head.next == null) { return head; } - Node prev = null; - Node h2 = reverseListRec(head.next); + SinglyLinkedListNode prev = null; + SinglyLinkedListNode h2 = reverseListRec(head.next); head.next.next = head; head.next = prev; @@ -167,7 +167,7 @@ public Node reverseListRec(Node head) { * Clear all nodes in the list */ public void clear() { - Node cur = head; + SinglyLinkedListNode cur = head; while (cur != null) { cur = cur.next; } @@ -198,7 +198,7 @@ public int size() { * * @return head of the list. */ - public Node getHead() { + public SinglyLinkedListNode getHead() { return head; } @@ -206,7 +206,7 @@ public Node getHead() { * Set head of the list. * */ - public void setHead(Node head) { + public void setHead(SinglyLinkedListNode head) { this.head = head; } @@ -249,10 +249,10 @@ public String toString() { } public void deleteDuplicates() { - Node pred = head; + SinglyLinkedListNode pred = head; // predecessor = the node // having sublist of its duplicates - Node newHead = head; + SinglyLinkedListNode newHead = head; while (newHead != null) { // if it's a beginning of duplicates sublist // skip all duplicates @@ -273,7 +273,7 @@ public void deleteDuplicates() { } public void print() { - Node temp = head; + SinglyLinkedListNode temp = head; while (temp != null && temp.next != null) { System.out.print(temp.value + "->"); temp = temp.next; @@ -310,7 +310,7 @@ public void insert(int data) { */ public void insertNth(int data, int position) { checkBounds(position, 0, size); - Node newNode = new Node(data); + SinglyLinkedListNode newNode = new SinglyLinkedListNode(data); if (head == null) { /* the list is empty */ head = newNode; @@ -325,7 +325,7 @@ public void insertNth(int data, int position) { return; } - Node cur = head; + SinglyLinkedListNode cur = head; for (int i = 0; i < position - 1; ++i) { cur = cur.next; } @@ -359,7 +359,7 @@ public void deleteNth(int position) { size--; return; } - Node cur = head; + SinglyLinkedListNode cur = head; for (int i = 0; i < position - 1; ++i) { cur = cur.next; } @@ -376,7 +376,7 @@ public void deleteNth(int position) { */ public int getNth(int index) { checkBounds(index, 0, size - 1); - Node cur = head; + SinglyLinkedListNode cur = head; for (int i = 0; i < index; ++i) { cur = cur.next; } @@ -403,7 +403,7 @@ public static void main(String[] arg) { SinglyLinkedList list = new SinglyLinkedList(); assert list.isEmpty(); assert list.size() == 0 && list.count() == 0; - assert list.toString().equals(""); + assert list.toString().isEmpty(); /* Test insert function */ list.insertHead(5); @@ -440,7 +440,7 @@ public static void main(String[] arg) { } SinglyLinkedList instance = new SinglyLinkedList(); - Node head = new Node(0, new Node(2, new Node(3, new Node(3, new Node(4))))); + SinglyLinkedListNode head = new SinglyLinkedListNode(0, new SinglyLinkedListNode(2, new SinglyLinkedListNode(3, new SinglyLinkedListNode(3, new SinglyLinkedListNode(4))))); instance.setHead(head); instance.deleteDuplicates(); instance.print(); @@ -452,7 +452,7 @@ public Iterator iterator() { } private class SinglyLinkedListIterator implements Iterator { - private Node current; + private SinglyLinkedListNode current; SinglyLinkedListIterator() { current = head; @@ -474,43 +474,3 @@ public Integer next() { } } } - -/** - * This class is the nodes of the SinglyLinked List. They consist of a value and - * a pointer to the node after them. - */ -class Node { - - /** - * The value of the node - */ - int value; - - /** - * Point to the next node - */ - Node next; - - Node() { - } - - /** - * Constructor - * - * @param value Value to be put in the node - */ - Node(int value) { - this(value, null); - } - - /** - * Constructor - * - * @param value Value to be put in the node - * @param next Reference to the next node - */ - Node(int value, Node next) { - this.value = value; - this.next = next; - } -} diff --git a/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedListNode.java b/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedListNode.java new file mode 100644 index 000000000000..d0a06369215a --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedListNode.java @@ -0,0 +1,34 @@ +package com.thealgorithms.datastructures.lists; + +/** + * This class is the nodes of the SinglyLinked List. They consist of a value and + * a pointer to the node after them. + */ +class SinglyLinkedListNode { + + int value; + SinglyLinkedListNode next = null; + + SinglyLinkedListNode() { + } + + /** + * Constructor + * + * @param value Value to be put in the node + */ + SinglyLinkedListNode(int value) { + this(value, null); + } + + /** + * Constructor + * + * @param value Value to be put in the node + * @param next Reference to the next node + */ + SinglyLinkedListNode(int value, SinglyLinkedListNode next) { + this.value = value; + this.next = next; + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/lists/SortedLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/SortedLinkedList.java index 4cf782679b7c..e515c9e4adc4 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/SortedLinkedList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/SortedLinkedList.java @@ -4,24 +4,42 @@ import java.util.List; /** - * A SortedLinkedList is a data structure that maintains a sorted list of elements. - * Elements are ordered based on their natural ordering or by a Comparator provided at the time of creation. - * This implementation uses a singly linked list to store the elements. - * Further details can be found on this link + * The SortedLinkedList class represents a singly linked list that maintains its elements in sorted order. + * Elements are ordered based on their natural ordering, with smaller elements at the head and larger elements toward the tail. + * The class provides methods for inserting, deleting, and searching elements, as well as checking if the list is empty. + *

+ * This implementation utilizes a singly linked list to maintain a dynamically sorted list. + *

+ *

+ * Further information can be found here: * https://runestone.academy/ns/books/published/cppds/LinearLinked/ImplementinganOrderedList.html + *

+ * + * Usage Example: + *
+ *     SortedLinkedList list = new SortedLinkedList();
+ *     list.insert(10);
+ *     list.insert(5);
+ *     list.insert(20);
+ *     System.out.println(list); // Outputs: [5, 10, 20]
+ * 
*/ public class SortedLinkedList { private Node head; private Node tail; + /** + * Initializes an empty sorted linked list. + */ public SortedLinkedList() { this.head = null; this.tail = null; } /** - * Inserts a new element into the sorted linked list. - * @param value the value to be inserted + * Inserts a new integer into the list, maintaining sorted order. + * + * @param value the integer to insert */ public void insert(int value) { Node newNode = new Node(value); @@ -48,16 +66,10 @@ public void insert(int value) { } /** - * Displays the elements of the sorted linked list. - */ - public void display() { - System.out.println(this.toString()); - } - - /** - * Deletes the first occurrence of the specified element in the sorted linked list. - * @param value the value to be deleted - * @return true if the element is found and deleted, false otherwise + * Deletes the first occurrence of a specified integer in the list. + * + * @param value the integer to delete + * @return {@code true} if the element was found and deleted; {@code false} otherwise */ public boolean delete(int value) { if (this.head == null) { @@ -87,9 +99,10 @@ public boolean delete(int value) { } /** - * Searches for the specified element in the sorted linked list. - * @param value the value to be searched - * @return true if the element is found, false otherwise + * Searches for a specified integer in the list. + * + * @param value the integer to search for + * @return {@code true} if the value is present in the list; {@code false} otherwise */ public boolean search(int value) { Node temp = this.head; @@ -103,14 +116,17 @@ public boolean search(int value) { } /** - * Checks if the sorted linked list is empty. - * @return true if the list is empty, false otherwise + * Checks if the list is empty. + * + * @return {@code true} if the list is empty; {@code false} otherwise */ public boolean isEmpty() { return head == null; } + /** - * Returns a string representation of the sorted linked list. + * Returns a string representation of the sorted linked list in the format [element1, element2, ...]. + * * @return a string representation of the sorted linked list */ @Override @@ -123,12 +139,14 @@ public String toString() { temp = temp.next; } return "[" + String.join(", ", elements) + "]"; - } else { return "[]"; } } + /** + * Node represents an element in the sorted linked list. + */ public final class Node { public final int value; public Node next; diff --git a/src/main/java/com/thealgorithms/datastructures/queues/CircularQueue.java b/src/main/java/com/thealgorithms/datastructures/queues/CircularQueue.java index c67817a6f2d4..74ee06ca92e4 100644 --- a/src/main/java/com/thealgorithms/datastructures/queues/CircularQueue.java +++ b/src/main/java/com/thealgorithms/datastructures/queues/CircularQueue.java @@ -1,7 +1,27 @@ package com.thealgorithms.datastructures.queues; -// This program implements the concept of CircularQueue in Java -// Link to the concept: (https://en.wikipedia.org/wiki/Circular_buffer) +/** + * The CircularQueue class represents a generic circular queue data structure that uses an array to + * store elements. This queue allows efficient utilization of space by wrapping around the array, + * thus avoiding the need to shift elements during enqueue and dequeue operations. + * + *

When the queue reaches its maximum capacity, further enqueues will raise an exception. + * Similarly, attempts to dequeue or peek from an empty queue will also result in an exception. + * + *

Reference: Circular Buffer + * + *

Usage Example: + *

+ *     CircularQueue queue = new CircularQueue<>(3);
+ *     queue.enQueue(1);
+ *     queue.enQueue(2);
+ *     queue.enQueue(3);
+ *     queue.deQueue(); // Removes 1
+ *     queue.enQueue(4); // Wraps around and places 4 at the position of removed 1
+ * 
+ * + * @param the type of elements in this queue + */ public class CircularQueue { private T[] array; private int topOfQueue; @@ -9,8 +29,17 @@ public class CircularQueue { private final int size; private int currentSize; + /** + * Constructs a CircularQueue with a specified capacity. + * + * @param size the maximum number of elements this queue can hold + * @throws IllegalArgumentException if the size is less than 1 + */ @SuppressWarnings("unchecked") public CircularQueue(int size) { + if (size < 1) { + throw new IllegalArgumentException("Size must be greater than 0"); + } this.array = (T[]) new Object[size]; this.topOfQueue = -1; this.beginningOfQueue = -1; @@ -18,14 +47,30 @@ public CircularQueue(int size) { this.currentSize = 0; } + /** + * Checks if the queue is empty. + * + * @return {@code true} if the queue is empty; {@code false} otherwise + */ public boolean isEmpty() { return currentSize == 0; } + /** + * Checks if the queue is full. + * + * @return {@code true} if the queue has reached its maximum capacity; {@code false} otherwise + */ public boolean isFull() { return currentSize == size; } + /** + * Adds a new element to the queue. If the queue is full, an exception is thrown. + * + * @param value the element to be added to the queue + * @throws IllegalStateException if the queue is already full + */ public void enQueue(T value) { if (isFull()) { throw new IllegalStateException("Queue is full"); @@ -38,12 +83,18 @@ public void enQueue(T value) { currentSize++; } + /** + * Removes and returns the element at the front of the queue. + * + * @return the element at the front of the queue + * @throws IllegalStateException if the queue is empty + */ public T deQueue() { if (isEmpty()) { throw new IllegalStateException("Queue is empty"); } T removedValue = array[beginningOfQueue]; - array[beginningOfQueue] = null; // Optional: Help GC + array[beginningOfQueue] = null; // Optional: Nullify to help garbage collection beginningOfQueue = (beginningOfQueue + 1) % size; currentSize--; if (isEmpty()) { @@ -53,6 +104,12 @@ public T deQueue() { return removedValue; } + /** + * Returns the element at the front of the queue without removing it. + * + * @return the element at the front of the queue + * @throws IllegalStateException if the queue is empty + */ public T peek() { if (isEmpty()) { throw new IllegalStateException("Queue is empty"); @@ -60,6 +117,9 @@ public T peek() { return array[beginningOfQueue]; } + /** + * Deletes the entire queue by resetting all elements and pointers. + */ public void deleteQueue() { array = null; beginningOfQueue = -1; @@ -67,35 +127,12 @@ public void deleteQueue() { currentSize = 0; } + /** + * Returns the current number of elements in the queue. + * + * @return the number of elements currently in the queue + */ public int size() { return currentSize; } - - public static void main(String[] args) { - CircularQueue cq = new CircularQueue<>(5); - System.out.println(cq.isEmpty()); // true - System.out.println(cq.isFull()); // false - cq.enQueue(1); - cq.enQueue(2); - cq.enQueue(3); - cq.enQueue(4); - cq.enQueue(5); - - System.out.println(cq.deQueue()); // 1 - System.out.println(cq.deQueue()); // 2 - System.out.println(cq.deQueue()); // 3 - System.out.println(cq.deQueue()); // 4 - System.out.println(cq.deQueue()); // 5 - - System.out.println(cq.isFull()); // false - System.out.println(cq.isEmpty()); // true - cq.enQueue(6); - cq.enQueue(7); - cq.enQueue(8); - - System.out.println(cq.peek()); // 6 - System.out.println(cq.peek()); // 6 - - cq.deleteQueue(); - } } diff --git a/src/main/java/com/thealgorithms/datastructures/queues/GenericArrayListQueue.java b/src/main/java/com/thealgorithms/datastructures/queues/GenericArrayListQueue.java index 2a3a5a2c38e2..865da7bffc9f 100644 --- a/src/main/java/com/thealgorithms/datastructures/queues/GenericArrayListQueue.java +++ b/src/main/java/com/thealgorithms/datastructures/queues/GenericArrayListQueue.java @@ -4,43 +4,46 @@ import java.util.List; /** - * This class implements a GenericArrayListQueue. + * This class implements a GenericArrayListQueue, a queue data structure that + * holds elements of any type specified at runtime, allowing flexibility in the type + * of elements it stores. * - * A GenericArrayListQueue data structure functions the same as any - * specific-typed queue. The GenericArrayListQueue holds elements of types - * to-be-specified at runtime. The elements that are added first are the first - * to be removed (FIFO). New elements are added to the back/rear of the queue. + *

The GenericArrayListQueue operates on a First-In-First-Out (FIFO) basis, where + * elements added first are the first to be removed. New elements are added to the back + * (or rear) of the queue, while removal of elements occurs from the front. + * + * @param The type of elements held in this queue. */ public class GenericArrayListQueue { /** - * The generic List for the queue. T is the generic element type. + * A list that stores the queue's elements in insertion order. */ private final List elementList = new ArrayList<>(); /** * Checks if the queue is empty. * - * @return True if the queue is empty, false otherwise. + * @return {@code true} if the queue has no elements; {@code false} otherwise. */ - private boolean isEmpty() { + public boolean isEmpty() { return elementList.isEmpty(); } /** - * Returns the element at the front of the queue without removing it. + * Retrieves, but does not remove, the element at the front of the queue. * - * @return The element at the front of the queue, or null if the queue is empty. + * @return The element at the front of the queue, or {@code null} if the queue is empty. */ public T peek() { return isEmpty() ? null : elementList.getFirst(); } /** - * Inserts an element of type T to the back of the queue. + * Inserts an element at the back of the queue. * - * @param element the element to be added to the queue. - * @return True if the element was added successfully. + * @param element The element to be added to the queue. + * @return {@code true} if the element was successfully added. */ public boolean add(T element) { return elementList.add(element); @@ -49,7 +52,7 @@ public boolean add(T element) { /** * Retrieves and removes the element at the front of the queue. * - * @return The element removed from the front of the queue, or null if the queue is empty. + * @return The element removed from the front of the queue, or {@code null} if the queue is empty. */ public T poll() { return isEmpty() ? null : elementList.removeFirst(); diff --git a/src/main/java/com/thealgorithms/datastructures/queues/QueueByTwoStacks.java b/src/main/java/com/thealgorithms/datastructures/queues/QueueByTwoStacks.java new file mode 100644 index 000000000000..11e5e9b83892 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/queues/QueueByTwoStacks.java @@ -0,0 +1,87 @@ +package com.thealgorithms.datastructures.queues; + +import java.util.NoSuchElementException; +import java.util.Stack; + +/** + * A queue implementation using two stacks. This class provides methods to + * enqueue (add) elements to the end of the queue and dequeue (remove) + * elements from the front, while utilizing two internal stacks to manage + * the order of elements. + * + * @param The type of elements held in this queue. + */ +public class QueueByTwoStacks { + + private final Stack enqueueStk; + private final Stack dequeueStk; + + /** + * Constructor that initializes two empty stacks for the queue. + * The `enqueueStk` is used to push elements when enqueuing, and + * the `dequeueStk` is used to pop elements when dequeuing. + */ + public QueueByTwoStacks() { + enqueueStk = new Stack<>(); + dequeueStk = new Stack<>(); + } + + /** + * Adds an element to the end of the queue. This method pushes the element + * onto the `enqueueStk`. + * + * @param item The element to be added to the queue. + */ + public void put(T item) { + enqueueStk.push(item); + } + + /** + * Removes and returns the element at the front of the queue. + * If `dequeueStk` is empty, it transfers all elements from + * `enqueueStk` to `dequeueStk` to maintain the correct FIFO + * (First-In-First-Out) order before popping. + * + * @return The element at the front of the queue. + * @throws NoSuchElementException If the queue is empty. + */ + public T get() { + if (dequeueStk.isEmpty()) { + while (!enqueueStk.isEmpty()) { + dequeueStk.push(enqueueStk.pop()); + } + } + if (dequeueStk.isEmpty()) { + throw new NoSuchElementException("Queue is empty"); + } + return dequeueStk.pop(); + } + + /** + * Returns the total number of elements currently in the queue. + * This is the sum of the sizes of both stacks. + * + * @return The number of elements in the queue. + */ + public int size() { + return enqueueStk.size() + dequeueStk.size(); + } + + /** + * Returns a string representation of the queue, showing the elements + * in the correct order (from front to back). + * The `dequeueStk` is first cloned, and then all elements from the + * `enqueueStk` are added to the cloned stack in reverse order to + * represent the queue accurately. + * + * @return A string representation of the queue. + */ + @Override + public String toString() { + Stack tempStack = (Stack) dequeueStk.clone(); + while (!enqueueStk.isEmpty()) { + tempStack.push(enqueueStk.pop()); + } + return "Queue(" + tempStack + ")"; + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/queues/SlidingWindowMaximum.java b/src/main/java/com/thealgorithms/datastructures/queues/SlidingWindowMaximum.java new file mode 100644 index 000000000000..d6720dd01e29 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/queues/SlidingWindowMaximum.java @@ -0,0 +1,63 @@ +package com.thealgorithms.datastructures.queues; + +import java.util.Deque; +import java.util.LinkedList; + +/** + * The {@code SlidingWindowMaximum} class provides a method to efficiently compute + * the maximum element within every sliding window of size {@code k} in a given array. + * + *

The algorithm uses a deque to maintain the indices of useful elements within + * the current sliding window. The time complexity of this approach is O(n) since + * each element is processed at most twice. + * + * @author Hardvan + */ +public final class SlidingWindowMaximum { + private SlidingWindowMaximum() { + } + + /** + * Returns an array of the maximum values for each sliding window of size {@code k}. + *

If {@code nums} has fewer elements than {@code k}, the result will be an empty array. + *

Example: + *

+     * Input: nums = [1, 3, -1, -3, 5, 3, 6, 7], k = 3
+     * Output: [3, 3, 5, 5, 6, 7]
+     * 
+ * + * @param nums the input array of integers + * @param k the size of the sliding window + * @return an array containing the maximum element for each sliding window + */ + public static int[] maxSlidingWindow(int[] nums, int k) { + int n = nums.length; + if (n < k || k == 0) { + return new int[0]; + } + + int[] result = new int[n - k + 1]; + Deque deque = new LinkedList<>(); + for (int i = 0; i < n; i++) { + // Remove elements from the front of the deque if they are out of the current window + if (!deque.isEmpty() && deque.peekFirst() < i - k + 1) { + deque.pollFirst(); + } + + // Remove elements from the back if they are smaller than the current element + while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) { + deque.pollLast(); + } + + // Add the current element's index to the deque + deque.offerLast(i); + + // Store the maximum element for the current window (starting from the k-1th element) + if (i >= k - 1) { + result[i - k + 1] = nums[deque.peekFirst()]; + } + } + + return result; + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/queues/TokenBucket.java b/src/main/java/com/thealgorithms/datastructures/queues/TokenBucket.java new file mode 100644 index 000000000000..999c963fab93 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/queues/TokenBucket.java @@ -0,0 +1,61 @@ +package com.thealgorithms.datastructures.queues; + +import java.util.concurrent.TimeUnit; + +/** + * TokenBucket implements a token bucket rate limiter algorithm. + * This class is used to control the rate of requests in a distributed system. + * It allows a certain number of requests (tokens) to be processed in a time frame, + * based on the defined refill rate. + * + * Applications: Computer networks, API rate limiting, distributed systems, etc. + * + * @author Hardvan + */ +public final class TokenBucket { + private final int maxTokens; + private final int refillRate; // tokens per second + private int tokens; + private long lastRefill; // Timestamp in nanoseconds + + /** + * Constructs a TokenBucket instance. + * + * @param maxTokens Maximum number of tokens the bucket can hold. + * @param refillRate The rate at which tokens are refilled (tokens per second). + */ + public TokenBucket(int maxTokens, int refillRate) { + this.maxTokens = maxTokens; + this.refillRate = refillRate; + this.tokens = maxTokens; + this.lastRefill = System.nanoTime(); + } + + /** + * Attempts to allow a request based on the available tokens. + * If a token is available, it decrements the token count and allows the request. + * Otherwise, the request is denied. + * + * @return true if the request is allowed, false if the request is denied. + */ + public synchronized boolean allowRequest() { + refillTokens(); + if (tokens > 0) { + tokens--; + return true; + } + return false; + } + + /** + * Refills the tokens based on the time elapsed since the last refill. + * The number of tokens to be added is calculated based on the elapsed time + * and the refill rate, ensuring the total does not exceed maxTokens. + */ + private void refillTokens() { + long now = System.nanoTime(); + long tokensToAdd = (now - lastRefill) / TimeUnit.SECONDS.toNanos(1) * refillRate; + tokens = Math.min(maxTokens, tokens + (int) tokensToAdd); + lastRefill = now; + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/stacks/NodeStack.java b/src/main/java/com/thealgorithms/datastructures/stacks/NodeStack.java index 7c4f334cd617..bbcdfe1cc2a8 100644 --- a/src/main/java/com/thealgorithms/datastructures/stacks/NodeStack.java +++ b/src/main/java/com/thealgorithms/datastructures/stacks/NodeStack.java @@ -1,161 +1,98 @@ package com.thealgorithms.datastructures.stacks; /** - * Implementation of a stack using nodes. Unlimited size, no arraylist. + * A stack implementation using linked nodes, supporting unlimited size without an ArrayList. * - * @author Kyler Smith, 2017 + *

Each node in the stack contains data of generic type {@code Item}, along with references + * to the next and previous nodes, supporting typical stack operations. + * + *

The stack follows a Last-In-First-Out (LIFO) order where elements added last are + * removed first. Supported operations include push, pop, and peek. + * + * @param the type of elements held in this stack */ public class NodeStack { /** - * Entry point for the program. + * Node class representing each element in the stack. */ - public static void main(String[] args) { - NodeStack stack = new NodeStack(); - - stack.push(3); - stack.push(4); - stack.push(5); - System.out.println("Testing :"); - stack.print(); // prints : 5 4 3 + private class Node { + Item data; + Node previous; - Integer x = stack.pop(); // x = 5 - stack.push(1); - stack.push(8); - Integer y = stack.peek(); // y = 8 - System.out.println("Testing :"); - stack.print(); // prints : 8 1 4 3 - - System.out.println("Testing :"); - System.out.println("x : " + x); - System.out.println("y : " + y); + Node(Item data) { + this.data = data; + this.previous = null; + } } - /** - * Information each node should contain. - * - * @value data : information of the value in the node - * @value head : the head of the stack - * @value next : the next value from this node - * @value previous : the last value from this node - * @value size : size of the stack - */ - private Item data; - - private static NodeStack head; - private NodeStack previous; - private static int size = 0; + private Node head; // Top node in the stack + private int size; // Number of elements in the stack /** - * Constructors for the NodeStack. + * Constructs an empty NodeStack. */ public NodeStack() { - } - - private NodeStack(Item item) { - this.data = item; + head = null; + size = 0; } /** - * Put a value onto the stack. + * Pushes an item onto the stack. * - * @param item : value to be put on the stack. + * @param item the item to be pushed onto the stack */ public void push(Item item) { - NodeStack newNs = new NodeStack(item); - - if (this.isEmpty()) { - NodeStack.setHead(new NodeStack<>(item)); - newNs.setNext(null); - newNs.setPrevious(null); - } else { - newNs.setPrevious(NodeStack.head); - NodeStack.head.setNext(newNs); - NodeStack.setHead(newNs); - } - - NodeStack.setSize(NodeStack.getSize() + 1); + Node newNode = new Node(item); + newNode.previous = head; + head = newNode; + size++; } /** - * Value to be taken off the stack. + * Removes and returns the item at the top of the stack. * - * @return item : value that is returned. + * @return the item at the top of the stack, or {@code null} if the stack is empty + * @throws IllegalStateException if the stack is empty */ public Item pop() { - Item item = (Item) NodeStack.head.getData(); - - NodeStack.setHead(NodeStack.head.getPrevious()); - NodeStack.head.setNext(null); - - NodeStack.setSize(NodeStack.getSize() - 1); - - return item; + if (isEmpty()) { + throw new IllegalStateException("Cannot pop from an empty stack."); + } + Item data = head.data; + head = head.previous; + size--; + return data; } /** - * Value that is next to be taken off the stack. + * Returns the item at the top of the stack without removing it. * - * @return item : the next value that would be popped off the stack. + * @return the item at the top of the stack, or {@code null} if the stack is empty + * @throws IllegalStateException if the stack is empty */ public Item peek() { - return (Item) NodeStack.head.getData(); + if (isEmpty()) { + throw new IllegalStateException("Cannot peek from an empty stack."); + } + return head.data; } /** - * If the stack is empty or there is a value in. + * Checks whether the stack is empty. * - * @return boolean : whether or not the stack has anything in it. + * @return {@code true} if the stack has no elements, {@code false} otherwise */ public boolean isEmpty() { - return NodeStack.getSize() == 0; + return head == null; } /** - * Returns the size of the stack. + * Returns the number of elements currently in the stack. * - * @return int : number of values in the stack. + * @return the size of the stack */ public int size() { - return NodeStack.getSize(); - } - - /** - * Print the contents of the stack in the following format. - * - *

- * x <- head (next out) y z <- tail (first in) . . . - */ - public void print() { - for (NodeStack n = NodeStack.head; n != null; n = n.previous) { - System.out.println(n.getData().toString()); - } - } - - private static void setHead(NodeStack ns) { - NodeStack.head = ns; - } - - private void setNext(NodeStack next) { - } - - private NodeStack getPrevious() { - return previous; - } - - private void setPrevious(NodeStack previous) { - this.previous = previous; - } - - private static int getSize() { return size; } - - private static void setSize(int size) { - NodeStack.size = size; - } - - private Item getData() { - return this.data; - } } diff --git a/src/main/java/com/thealgorithms/datastructures/stacks/ReverseStack.java b/src/main/java/com/thealgorithms/datastructures/stacks/ReverseStack.java index 84e9df96e0d9..e9de14b53302 100644 --- a/src/main/java/com/thealgorithms/datastructures/stacks/ReverseStack.java +++ b/src/main/java/com/thealgorithms/datastructures/stacks/ReverseStack.java @@ -1,10 +1,25 @@ package com.thealgorithms.datastructures.stacks; -import java.util.Scanner; import java.util.Stack; /** - * Reversal of a stack using recursion. + * Provides methods to reverse a stack using recursion. + * + *

This class includes methods to reverse the order of elements in a stack + * without using additional data structures. Elements are inserted at the bottom + * of the stack to achieve the reverse order. + * + *

Example usage: + *

+ *     Stack stack = new Stack<>();
+ *     stack.push(1);
+ *     stack.push(2);
+ *     stack.push(3);
+ *     ReverseStack.reverseStack(stack);
+ * 
+ * After calling {@code reverseStack(stack)}, the stack's order is reversed. + * + *

This class is final and has a private constructor to prevent instantiation. * * @author Ishika Agarwal, 2021 */ @@ -12,56 +27,48 @@ public final class ReverseStack { private ReverseStack() { } - public static void main(String[] args) { - try (Scanner sc = new Scanner(System.in)) { - System.out.println("Enter the number of elements you wish to insert in the stack"); - int n = sc.nextInt(); - int i; - Stack stack = new Stack(); - System.out.println("Enter the stack elements"); - for (i = 0; i < n; i++) { - stack.push(sc.nextInt()); - } - reverseStack(stack); - System.out.println("The reversed stack is:"); - while (!stack.isEmpty()) { - System.out.print(stack.peek() + ","); - stack.pop(); - } - } - } - - private static void reverseStack(Stack stack) { + /** + * Reverses the order of elements in the given stack using recursion. + * Steps: + * 1. Check if the stack is empty. If so, return. + * 2. Pop the top element from the stack. + * 3. Recursively reverse the remaining stack. + * 4. Insert the originally popped element at the bottom of the reversed stack. + * + * @param stack the stack to reverse; should not be null + */ + public static void reverseStack(Stack stack) { if (stack.isEmpty()) { return; } - // Store the topmost element - int element = stack.peek(); - // Remove the topmost element - stack.pop(); - - // Reverse the stack for the leftover elements + int element = stack.pop(); reverseStack(stack); - - // Insert the topmost element to the bottom of the stack insertAtBottom(stack, element); } + /** + * Inserts the specified element at the bottom of the stack. + * + *

This method is a helper for {@link #reverseStack(Stack)}. + * + * Steps: + * 1. If the stack is empty, push the element and return. + * 2. Remove the top element from the stack. + * 3. Recursively insert the new element at the bottom of the stack. + * 4. Push the removed element back onto the stack. + * + * @param stack the stack in which to insert the element; should not be null + * @param element the element to insert at the bottom of the stack + */ private static void insertAtBottom(Stack stack, int element) { if (stack.isEmpty()) { - // When stack is empty, insert the element so it will be present at - // the bottom of the stack stack.push(element); return; } - int ele = stack.peek(); - // Keep popping elements till stack becomes empty. Push the elements - // once the topmost element has moved to the bottom of the stack. - stack.pop(); + int topElement = stack.pop(); insertAtBottom(stack, element); - - stack.push(ele); + stack.push(topElement); } } diff --git a/src/main/java/com/thealgorithms/datastructures/stacks/StackArray.java b/src/main/java/com/thealgorithms/datastructures/stacks/StackArray.java index f98db7cc1550..9369b3fc9a64 100644 --- a/src/main/java/com/thealgorithms/datastructures/stacks/StackArray.java +++ b/src/main/java/com/thealgorithms/datastructures/stacks/StackArray.java @@ -1,7 +1,13 @@ package com.thealgorithms.datastructures.stacks; /** - * This class implements a Stack using a regular array. + * Implements a generic stack using an array. + * + *

This stack automatically resizes when necessary, growing to accommodate additional elements and + * shrinking to conserve memory when its size significantly decreases. + * + *

Elements are pushed and popped in LIFO (last-in, first-out) order, where the last element added + * is the first to be removed. * * @param the type of elements in this stack */ @@ -13,11 +19,20 @@ public class StackArray implements Stack { private T[] stackArray; private int top; + /** + * Creates a stack with a default capacity. + */ @SuppressWarnings("unchecked") public StackArray() { this(DEFAULT_CAPACITY); } + /** + * Creates a stack with a specified initial capacity. + * + * @param size the initial capacity of the stack, must be greater than 0 + * @throws IllegalArgumentException if size is less than or equal to 0 + */ @SuppressWarnings("unchecked") public StackArray(int size) { if (size <= 0) { @@ -28,6 +43,11 @@ public StackArray(int size) { this.top = -1; } + /** + * Pushes an element onto the top of the stack. Resizes the stack if it is full. + * + * @param value the element to push + */ @Override public void push(T value) { if (isFull()) { @@ -36,6 +56,13 @@ public void push(T value) { stackArray[++top] = value; } + /** + * Removes and returns the element from the top of the stack. Shrinks the stack if + * its size is below a quarter of its capacity, but not below the default capacity. + * + * @return the element removed from the top of the stack + * @throws IllegalStateException if the stack is empty + */ @Override public T pop() { if (isEmpty()) { @@ -48,6 +75,12 @@ public T pop() { return value; } + /** + * Returns the element at the top of the stack without removing it. + * + * @return the top element of the stack + * @throws IllegalStateException if the stack is empty + */ @Override public T peek() { if (isEmpty()) { @@ -56,6 +89,11 @@ public T peek() { return stackArray[top]; } + /** + * Resizes the internal array to a new capacity. + * + * @param newSize the new size of the stack array + */ private void resize(int newSize) { @SuppressWarnings("unchecked") T[] newArray = (T[]) new Object[newSize]; System.arraycopy(stackArray, 0, newArray, 0, top + 1); @@ -63,21 +101,60 @@ private void resize(int newSize) { maxSize = newSize; } + /** + * Checks if the stack is full. + * + * @return true if the stack is full, false otherwise + */ public boolean isFull() { return top + 1 == maxSize; } + /** + * Checks if the stack is empty. + * + * @return true if the stack is empty, false otherwise + */ @Override public boolean isEmpty() { return top == -1; } - @Override public void makeEmpty() { // Doesn't delete elements in the array but if you call - top = -1; // push method after calling makeEmpty it will overwrite previous values + /** + * Empties the stack, marking it as empty without deleting elements. Elements are + * overwritten on subsequent pushes. + */ + @Override + public void makeEmpty() { + top = -1; } + /** + * Returns the number of elements currently in the stack. + * + * @return the size of the stack + */ @Override public int size() { return top + 1; } + + /** + * Returns a string representation of the stack. + * + * @return a string representation of the stack + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("StackArray ["); + for (int i = 0; i <= top; i++) { + sb.append(stackArray[i]); + if (i < top) { + sb.append(", "); + } + } + sb.append("]"); + return sb.toString(); + } } diff --git a/src/main/java/com/thealgorithms/datastructures/stacks/StackArrayList.java b/src/main/java/com/thealgorithms/datastructures/stacks/StackArrayList.java index 088156a98f78..bd400adea317 100644 --- a/src/main/java/com/thealgorithms/datastructures/stacks/StackArrayList.java +++ b/src/main/java/com/thealgorithms/datastructures/stacks/StackArrayList.java @@ -4,23 +4,41 @@ import java.util.EmptyStackException; /** - * This class implements a Stack using an ArrayList. + * A stack implementation backed by an {@link ArrayList}, offering dynamic resizing + * and LIFO (Last-In-First-Out) behavior. * - * @param the type of elements in this stack + *

The stack grows dynamically as elements are added, and elements are removed + * in reverse order of their addition. + * + * @param the type of elements stored in this stack */ public class StackArrayList implements Stack { private final ArrayList stack; + /** + * Constructs an empty stack. + */ public StackArrayList() { stack = new ArrayList<>(); } + /** + * Adds an element to the top of the stack. + * + * @param value the element to be added + */ @Override public void push(T value) { stack.add(value); } + /** + * Removes and returns the element from the top of the stack. + * + * @return the element removed from the top of the stack + * @throws EmptyStackException if the stack is empty + */ @Override public T pop() { if (isEmpty()) { @@ -29,6 +47,12 @@ public T pop() { return stack.removeLast(); } + /** + * Returns the element at the top of the stack without removing it. + * + * @return the top element of the stack + * @throws EmptyStackException if the stack is empty + */ @Override public T peek() { if (isEmpty()) { @@ -37,16 +61,29 @@ public T peek() { return stack.getLast(); } + /** + * Checks if the stack is empty. + * + * @return {@code true} if the stack is empty, {@code false} otherwise + */ @Override public boolean isEmpty() { return stack.isEmpty(); } + /** + * Empties the stack, removing all elements. + */ @Override public void makeEmpty() { stack.clear(); } + /** + * Returns the number of elements in the stack. + * + * @return the current size of the stack + */ @Override public int size() { return stack.size(); diff --git a/src/main/java/com/thealgorithms/datastructures/stacks/StackOfLinkedList.java b/src/main/java/com/thealgorithms/datastructures/stacks/StackOfLinkedList.java index 52b1c1d86c94..c12097dfa28c 100644 --- a/src/main/java/com/thealgorithms/datastructures/stacks/StackOfLinkedList.java +++ b/src/main/java/com/thealgorithms/datastructures/stacks/StackOfLinkedList.java @@ -3,35 +3,20 @@ import java.util.NoSuchElementException; /** - * @author Varun Upadhyay (https://github.com/varunu28) + * A stack implementation using a singly linked list. + * + *

This class provides methods to push, pop, and peek elements in a Last-In-First-Out (LIFO) manner. + * It keeps track of the number of elements in the stack and allows checking if the stack is empty. + * + *

This implementation does not allow null elements to be pushed onto the stack. */ -// An implementation of a Stack using a Linked List final class StackOfLinkedList { private StackOfLinkedList() { } - - public static void main(String[] args) { - LinkedListStack stack = new LinkedListStack(); - stack.push(1); - stack.push(2); - stack.push(3); - stack.push(4); - stack.push(5); - - System.out.println(stack); - - System.out.println("Size of stack currently is: " + stack.getSize()); - - assert stack.pop() == 5; - assert stack.pop() == 4; - - System.out.println("Top element of stack currently is: " + stack.peek()); - } } -// A node class +// A node class for the linked list class Node { - public int data; public Node next; @@ -42,25 +27,24 @@ class Node { } /** - * A class which implements a stack using a linked list + * A class that implements a stack using a linked list. * - *

- * Contains all the stack methods : push, pop, printStack, isEmpty + *

This stack supports basic operations: + *

    + *
  • push: Adds an element to the top of the stack
  • + *
  • pop: Removes and returns the top element of the stack
  • + *
  • peek: Returns the top element without removing it
  • + *
  • isEmpty: Checks if the stack is empty
  • + *
  • getSize: Returns the current size of the stack
  • + *
*/ class LinkedListStack { - /** - * Top of stack - */ - Node head; + private Node head; // Top of the stack + private int size; // Number of elements in the stack /** - * Size of stack - */ - private int size; - - /** - * Init properties + * Initializes an empty stack. */ LinkedListStack() { head = null; @@ -68,10 +52,10 @@ class LinkedListStack { } /** - * Add element at top + * Adds an element to the top of the stack. * - * @param x to be added - * @return true if add successfully + * @param x the element to be added + * @return true if the element is added successfully */ public boolean push(int x) { Node newNode = new Node(x); @@ -82,10 +66,10 @@ public boolean push(int x) { } /** - * Pop element at top of stack + * Removes and returns the top element of the stack. * - * @return element at top of stack - * @throws NoSuchElementException if stack is empty + * @return the element at the top of the stack + * @throws NoSuchElementException if the stack is empty */ public int pop() { if (size == 0) { @@ -94,20 +78,20 @@ public int pop() { Node destroy = head; head = head.next; int retValue = destroy.data; - destroy = null; // clear to let GC do it's work + destroy = null; // Help garbage collection size--; return retValue; } /** - * Peek element at top of stack + * Returns the top element of the stack without removing it. * - * @return element at top of stack - * @throws NoSuchElementException if stack is empty + * @return the element at the top of the stack + * @throws NoSuchElementException if the stack is empty */ public int peek() { if (size == 0) { - throw new NoSuchElementException("Empty stack. Nothing to pop"); + throw new NoSuchElementException("Empty stack. Nothing to peek"); } return head.data; } @@ -120,24 +104,32 @@ public String toString() { builder.append(cur.data).append("->"); cur = cur.next; } - return builder.replace(builder.length() - 2, builder.length(), "").toString(); + return builder.replace(builder.length() - 2, builder.length(), "").toString(); // Remove the last "->" } /** - * Check if stack is empty + * Checks if the stack is empty. * - * @return true if stack is empty, otherwise false + * @return true if the stack is empty, false otherwise */ public boolean isEmpty() { return size == 0; } /** - * Return size of stack + * Returns the current size of the stack. * - * @return size of stack + * @return the number of elements in the stack */ public int getSize() { return size; } + + /** + * Removes all elements from the stack. + */ + public void makeEmpty() { + head = null; + size = 0; + } } diff --git a/src/main/java/com/thealgorithms/datastructures/trees/AVLTree.java b/src/main/java/com/thealgorithms/datastructures/trees/AVLTree.java index 7b959b085353..77ee5d5fa23e 100644 --- a/src/main/java/com/thealgorithms/datastructures/trees/AVLTree.java +++ b/src/main/java/com/thealgorithms/datastructures/trees/AVLTree.java @@ -1,11 +1,19 @@ package com.thealgorithms.datastructures.trees; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents an AVL Tree, a self-balancing binary search tree. + * In an AVL tree, the heights of the two child subtrees of any node + * differ by at most one. If they differ by more than one at any time, + * rebalancing is performed to restore this property. + */ public class AVLTree { private Node root; - private class Node { - + private static class Node { private int key; private int balance; private int height; @@ -17,8 +25,18 @@ private class Node { key = k; parent = p; } + + public Integer getBalance() { + return balance; + } } + /** + * Inserts a new key into the AVL tree. + * + * @param key the key to be inserted + * @return {@code true} if the key was inserted, {@code false} if the key already exists + */ public boolean insert(int key) { if (root == null) { root = new Node(key, null); @@ -31,7 +49,6 @@ public boolean insert(int key) { } parent = n; - boolean goLeft = n.key > key; n = goLeft ? n.left : n.right; @@ -49,8 +66,32 @@ public boolean insert(int key) { return true; } + /** + * Deletes a key from the AVL tree. + * + * @param delKey the key to be deleted + */ + public void delete(int delKey) { + if (root == null) { + return; + } + + // Find the node to be deleted + Node node = root; + Node child = root; + while (child != null) { + node = child; + child = delKey >= node.key ? node.right : node.left; + if (delKey == node.key) { + delete(node); + return; + } + } + } + private void delete(Node node) { if (node.left == null && node.right == null) { + // Leaf node if (node.parent == null) { root = null; } else { @@ -64,6 +105,8 @@ private void delete(Node node) { } return; } + + // Node has one or two children Node child; if (node.left != null) { child = node.left; @@ -80,26 +123,49 @@ private void delete(Node node) { delete(child); } - public void delete(int delKey) { - if (root == null) { - return; + /** + * Returns a list of balance factors for each node in the tree. + * + * @return a list of integers representing the balance factors of the nodes + */ + public List returnBalance() { + List balances = new ArrayList<>(); + returnBalance(root, balances); + return balances; + } + + private void returnBalance(Node n, List balances) { + if (n != null) { + returnBalance(n.left, balances); + balances.add(n.getBalance()); + returnBalance(n.right, balances); } - Node node = root; - Node child = root; + } - while (child != null) { - node = child; - child = delKey >= node.key ? node.right : node.left; - if (delKey == node.key) { - delete(node); - return; - } + /** + * Searches for a key in the AVL tree. + * + * @param key the key to be searched + * @return true if the key is found, false otherwise + */ + public boolean search(int key) { + Node result = searchHelper(this.root, key); + return result != null; + } + + private Node searchHelper(Node root, int key) { + if (root == null || root.key == key) { + return root; } + + if (root.key > key) { + return searchHelper(root.left, key); + } + return searchHelper(root.right, key); } private void rebalance(Node n) { setBalance(n); - if (n.balance == -2) { if (height(n.left.left) >= height(n.left.right)) { n = rotateRight(n); @@ -143,7 +209,6 @@ private Node rotateLeft(Node a) { } setBalance(a, b); - return b; } @@ -169,7 +234,6 @@ private Node rotateRight(Node a) { } setBalance(a, b); - return b; } @@ -197,53 +261,9 @@ private void setBalance(Node... nodes) { } } - public void printBalance() { - printBalance(root); - } - - private void printBalance(Node n) { - if (n != null) { - printBalance(n.left); - System.out.printf("%s ", n.balance); - printBalance(n.right); - } - } - private void reheight(Node node) { if (node != null) { node.height = 1 + Math.max(height(node.left), height(node.right)); } } - - public boolean search(int key) { - Node result = searchHelper(this.root, key); - return result != null; - } - - private Node searchHelper(Node root, int key) { - // root is null or key is present at root - if (root == null || root.key == key) { - return root; - } - - // key is greater than root's key - if (root.key > key) { - return searchHelper(root.left, key); // call the function on the node's left child - } - // key is less than root's key then - // call the function on the node's right child as it is greater - return searchHelper(root.right, key); - } - - public static void main(String[] args) { - AVLTree tree = new AVLTree(); - - System.out.println("Inserting values 1 to 10"); - for (int i = 1; i < 10; i++) { - tree.insert(i); - } - - System.out.print("Printing balance: "); - tree.printBalance(); - } } diff --git a/src/main/java/com/thealgorithms/datastructures/trees/BinaryTree.java b/src/main/java/com/thealgorithms/datastructures/trees/BinaryTree.java index d4d677a4cda0..ff02fe38970b 100644 --- a/src/main/java/com/thealgorithms/datastructures/trees/BinaryTree.java +++ b/src/main/java/com/thealgorithms/datastructures/trees/BinaryTree.java @@ -144,7 +144,7 @@ public boolean remove(int value) { if (temp == root) { root = null; } // This if/else assigns the new node to be either the left or right child of the - // parent + // parent else if (temp.parent.data < temp.data) { temp.parent.right = null; } else { @@ -281,7 +281,7 @@ public void preOrder(Node localRoot) { } /** - * Prints rightChild - leftChild - root + * Prints leftChild - rightChild - root * * @param localRoot The local root of the binary tree */ diff --git a/src/main/java/com/thealgorithms/datastructures/trees/BoundaryTraversal.java b/src/main/java/com/thealgorithms/datastructures/trees/BoundaryTraversal.java new file mode 100644 index 000000000000..9a79902cc598 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/trees/BoundaryTraversal.java @@ -0,0 +1,168 @@ +package com.thealgorithms.datastructures.trees; + +import java.util.ArrayList; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; + +/** + * BoundaryTraversal + *

+ * Start with the Root: + * Add the root node to the boundary list. + * Traverse the Left Boundary (Excluding Leaf Nodes): + * Move down the left side of the tree, adding each non-leaf node to the boundary list. + * If a node has a left child, go left; otherwise, go right. + * Visit All Leaf Nodes: + * Traverse the tree and add all leaf nodes to the boundary list, from left to right. + * Traverse the Right Boundary (Excluding Leaf Nodes) in Reverse Order: + * Move up the right side of the tree, adding each non-leaf node to a temporary list. + * If a node has a right child, go right; otherwise, go left. + * Reverse the temporary list and add it to the boundary list. + * Combine and Output: + * The final boundary list contains the root, left boundary, leaf nodes, and reversed right boundary in that order. + */ +public final class BoundaryTraversal { + private BoundaryTraversal() { + } + + // Main function for boundary traversal, returns a list of boundary nodes in order + public static List boundaryTraversal(BinaryTree.Node root) { + List result = new ArrayList<>(); + if (root == null) { + return result; + } + + // Add root node if it's not a leaf node + if (!isLeaf(root)) { + result.add(root.data); + } + + // Add left boundary + addLeftBoundary(root, result); + + // Add leaf nodes + addLeaves(root, result); + + // Add right boundary + addRightBoundary(root, result); + + return result; + } + + // Adds the left boundary, including nodes that have no left child but have a right child + private static void addLeftBoundary(BinaryTree.Node node, List result) { + BinaryTree.Node cur = node.left; + + // If there is no left child but there is a right child, treat the right child as part of the left boundary + if (cur == null && node.right != null) { + cur = node.right; + } + + while (cur != null) { + if (!isLeaf(cur)) { + result.add(cur.data); // Add non-leaf nodes to result + } + if (cur.left != null) { + cur = cur.left; // Move to the left child + } else if (cur.right != null) { + cur = cur.right; // If left child is null, move to the right child + } else { + break; // Stop if there are no children + } + } + } + + // Adds leaf nodes (nodes without children) + private static void addLeaves(BinaryTree.Node node, List result) { + if (node == null) { + return; + } + if (isLeaf(node)) { + result.add(node.data); // Add leaf node + } else { + addLeaves(node.left, result); // Recur for left subtree + addLeaves(node.right, result); // Recur for right subtree + } + } + + // Adds the right boundary, excluding leaf nodes + private static void addRightBoundary(BinaryTree.Node node, List result) { + BinaryTree.Node cur = node.right; + List temp = new ArrayList<>(); + + // If no right boundary is present and there is no left subtree, skip + if (cur != null && node.left == null) { + return; + } + while (cur != null) { + if (!isLeaf(cur)) { + temp.add(cur.data); // Store non-leaf nodes temporarily + } + if (cur.right != null) { + cur = cur.right; // Move to the right child + } else if (cur.left != null) { + cur = cur.left; // If right child is null, move to the left child + } else { + break; // Stop if there are no children + } + } + + // Add the right boundary nodes in reverse order + for (int i = temp.size() - 1; i >= 0; i--) { + result.add(temp.get(i)); + } + } + + // Checks if a node is a leaf node + private static boolean isLeaf(BinaryTree.Node node) { + return node.left == null && node.right == null; + } + + // Iterative boundary traversal + public static List iterativeBoundaryTraversal(BinaryTree.Node root) { + List result = new ArrayList<>(); + if (root == null) { + return result; + } + + // Add root node if it's not a leaf node + if (!isLeaf(root)) { + result.add(root.data); + } + + // Handle the left boundary + BinaryTree.Node cur = root.left; + if (cur == null && root.right != null) { + cur = root.right; + } + while (cur != null) { + if (!isLeaf(cur)) { + result.add(cur.data); // Add non-leaf nodes to result + } + cur = (cur.left != null) ? cur.left : cur.right; // Prioritize left child, move to right if left is null + } + + // Add leaf nodes + addLeaves(root, result); + + // Handle the right boundary using a stack (reverse order) + cur = root.right; + Deque stack = new LinkedList<>(); + if (cur != null && root.left == null) { + return result; + } + while (cur != null) { + if (!isLeaf(cur)) { + stack.push(cur.data); // Temporarily store right boundary nodes in a stack + } + cur = (cur.right != null) ? cur.right : cur.left; // Prioritize right child, move to left if right is null + } + + // Add the right boundary nodes from the stack to maintain the correct order + while (!stack.isEmpty()) { + result.add(stack.pop()); + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/trees/GenericTree.java b/src/main/java/com/thealgorithms/datastructures/trees/GenericTree.java index a2e36f33dd4b..7d969a59def0 100644 --- a/src/main/java/com/thealgorithms/datastructures/trees/GenericTree.java +++ b/src/main/java/com/thealgorithms/datastructures/trees/GenericTree.java @@ -23,6 +23,7 @@ private static final class Node { } private final Node root; + public GenericTree() { // Constructor Scanner scn = new Scanner(System.in); root = createTreeG(null, 0, scn); @@ -225,8 +226,6 @@ private void removeleaves(Node node) { for (int i = 0; i < node.child.size(); i++) { if (node.child.get(i).child.size() == 0) { arr.add(i); - // node.child.remove(i); - // i--; } else { removeleaves(node.child.get(i)); } diff --git a/src/main/java/com/thealgorithms/datastructures/trees/QuadTree.java b/src/main/java/com/thealgorithms/datastructures/trees/QuadTree.java new file mode 100644 index 000000000000..e0d255b1e784 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/trees/QuadTree.java @@ -0,0 +1,176 @@ +package com.thealgorithms.datastructures.trees; + +import java.util.ArrayList; +import java.util.List; + +/** + * Point is a simple class that represents a point in 2D space. + * + * @see Point + * @author Sailok Chinta + */ +class Point { + public double x; + public double y; + + Point(double x, double y) { + this.x = x; + this.y = y; + } +} + +/** + * BoundingBox is a simple class that represents a bounding box in 2D space. + * + * @see Bounding Box + * @author Sailok Chinta + */ +class BoundingBox { + public Point center; + public double halfWidth; + + BoundingBox(Point center, double halfWidth) { + this.center = center; + this.halfWidth = halfWidth; + } + + /** + * Checks if the point is inside the bounding box + * + * @param point The point to check + * @return true if the point is inside the bounding box, false otherwise + */ + public boolean containsPoint(Point point) { + return point.x >= center.x - halfWidth && point.x <= center.x + halfWidth && point.y >= center.y - halfWidth && point.y <= center.y + halfWidth; + } + + /** + * Checks if the bounding box intersects with the other bounding box + * + * @param otherBoundingBox The other bounding box + * @return true if the bounding box intersects with the other bounding box, false otherwise + */ + public boolean intersectsBoundingBox(BoundingBox otherBoundingBox) { + return otherBoundingBox.center.x - otherBoundingBox.halfWidth <= center.x + halfWidth && otherBoundingBox.center.x + otherBoundingBox.halfWidth >= center.x - halfWidth && otherBoundingBox.center.y - otherBoundingBox.halfWidth <= center.y + halfWidth + && otherBoundingBox.center.y + otherBoundingBox.halfWidth >= center.y - halfWidth; + } +} + +/** + * QuadTree is a tree data structure that is used to store spatial information + * in an efficient way. + * + * This implementation is specific to Point QuadTrees + * + * @see Quad Tree + * @author Sailok Chinta + */ +public class QuadTree { + private final BoundingBox boundary; + private final int capacity; + + private List pointList; + private boolean divided; + private QuadTree northWest; + private QuadTree northEast; + private QuadTree southWest; + private QuadTree southEast; + + public QuadTree(BoundingBox boundary, int capacity) { + this.boundary = boundary; + this.capacity = capacity; + + this.pointList = new ArrayList<>(); + this.divided = false; + this.northWest = null; + this.northEast = null; + this.southWest = null; + this.southEast = null; + } + + /** + * Inserts a point into the tree + * + * @param point The point to insert + * @return true if the point is successfully inserted, false otherwise + */ + public boolean insert(Point point) { + if (point == null) { + return false; + } + + // Ignore points that don't belong to this quad tree + if (!boundary.containsPoint(point)) { + return false; + } + + // if the space is not already occupied, add it to the list + if (pointList.size() < capacity) { + pointList.add(point); + return true; + } + + // if subdivision hasn't happened, divide the tree + if (!divided) { + subDivide(); + } + + // try to add the point in one of the four quadrants + if (northWest.insert(point)) { + return true; + } + + if (northEast.insert(point)) { + return true; + } + + if (southWest.insert(point)) { + return true; + } + + if (southEast.insert(point)) { + return true; + } + + return false; + } + + /** + * Create four children that fully divide this quad into four quads of equal area + */ + private void subDivide() { + double quadrantHalfWidth = boundary.halfWidth / 2; + + northWest = new QuadTree(new BoundingBox(new Point(boundary.center.x - quadrantHalfWidth, boundary.center.y + quadrantHalfWidth), quadrantHalfWidth), this.capacity); + northEast = new QuadTree(new BoundingBox(new Point(boundary.center.x + quadrantHalfWidth, boundary.center.y + quadrantHalfWidth), quadrantHalfWidth), this.capacity); + southWest = new QuadTree(new BoundingBox(new Point(boundary.center.x - quadrantHalfWidth, boundary.center.y - quadrantHalfWidth), quadrantHalfWidth), this.capacity); + southEast = new QuadTree(new BoundingBox(new Point(boundary.center.x + quadrantHalfWidth, boundary.center.y - quadrantHalfWidth), quadrantHalfWidth), this.capacity); + divided = true; + } + + /** + * Queries all the points that intersect with the other bounding box + * + * @param otherBoundingBox The other bounding box + * @return List of points that intersect with the other bounding box + */ + public List query(BoundingBox otherBoundingBox) { + List points = new ArrayList<>(); + + if (!boundary.intersectsBoundingBox(otherBoundingBox)) { + return points; + } + + // filter the points that intersect with the other bounding box + points.addAll(pointList.stream().filter(otherBoundingBox::containsPoint).toList()); + + if (divided) { + points.addAll(northWest.query(otherBoundingBox)); + points.addAll(northEast.query(otherBoundingBox)); + points.addAll(southWest.query(otherBoundingBox)); + points.addAll(southEast.query(otherBoundingBox)); + } + + return points; + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/trees/TrieImp.java b/src/main/java/com/thealgorithms/datastructures/trees/Trie.java similarity index 50% rename from src/main/java/com/thealgorithms/datastructures/trees/TrieImp.java rename to src/main/java/com/thealgorithms/datastructures/trees/Trie.java index a43a454146cb..02f28d4d83ad 100644 --- a/src/main/java/com/thealgorithms/datastructures/trees/TrieImp.java +++ b/src/main/java/com/thealgorithms/datastructures/trees/Trie.java @@ -1,5 +1,28 @@ package com.thealgorithms.datastructures.trees; +import java.util.HashMap; + +/** + * Represents a Trie Node that stores a character and pointers to its children. + * Each node has a hashmap which can point to all possible characters. + * Each node also has a boolean value to indicate if it is the end of a word. + */ +class TrieNode { + char value; + HashMap child; + boolean end; + + /** + * Constructor to initialize a TrieNode with an empty hashmap + * and set end to false. + */ + TrieNode(char value) { + this.value = value; + this.child = new HashMap<>(); + this.end = false; + } +} + /** * Trie Data structure implementation without any libraries. *

@@ -14,27 +37,11 @@ * possible character. * * @author Dheeraj Kumar Barnwal + * @author Sailok Chinta */ -public class TrieImp { - /** - * Represents a Trie Node that stores a character and pointers to its children. - * Each node has an array of 26 children (one for each letter from 'a' to 'z'). - */ - public class TrieNode { - - TrieNode[] child; - boolean end; - - /** - * Constructor to initialize a TrieNode with an empty child array - * and set end to false. - */ - public TrieNode() { - child = new TrieNode[26]; - end = false; - } - } +public class Trie { + private static final char ROOT_NODE_VALUE = '*'; private final TrieNode root; @@ -42,8 +49,8 @@ public TrieNode() { * Constructor to initialize the Trie. * The root node is created but doesn't represent any character. */ - public TrieImp() { - root = new TrieNode(); + public Trie() { + root = new TrieNode(ROOT_NODE_VALUE); } /** @@ -57,13 +64,15 @@ public TrieImp() { public void insert(String word) { TrieNode currentNode = root; for (int i = 0; i < word.length(); i++) { - TrieNode node = currentNode.child[word.charAt(i) - 'a']; + TrieNode node = currentNode.child.getOrDefault(word.charAt(i), null); + if (node == null) { - node = new TrieNode(); - currentNode.child[word.charAt(i) - 'a'] = node; + node = new TrieNode(word.charAt(i)); + currentNode.child.put(word.charAt(i), node); } currentNode = node; } + currentNode.end = true; } @@ -80,13 +89,14 @@ public void insert(String word) { public boolean search(String word) { TrieNode currentNode = root; for (int i = 0; i < word.length(); i++) { - char ch = word.charAt(i); - TrieNode node = currentNode.child[ch - 'a']; + TrieNode node = currentNode.child.getOrDefault(word.charAt(i), null); + if (node == null) { return false; } currentNode = node; } + return currentNode.end; } @@ -104,40 +114,89 @@ public boolean search(String word) { public boolean delete(String word) { TrieNode currentNode = root; for (int i = 0; i < word.length(); i++) { - char ch = word.charAt(i); - TrieNode node = currentNode.child[ch - 'a']; + TrieNode node = currentNode.child.getOrDefault(word.charAt(i), null); if (node == null) { return false; } + currentNode = node; } + if (currentNode.end) { currentNode.end = false; return true; } + return false; } /** - * Helper method to print a string to the console. + * Counts the number of words in the trie + *

+ * The method traverses the Trie and counts the number of words. * - * @param print The string to be printed. + * @return count of words */ - public static void sop(String print) { - System.out.println(print); + public int countWords() { + return countWords(root); + } + + private int countWords(TrieNode node) { + if (node == null) { + return 0; + } + + int count = 0; + if (node.end) { + count++; + } + + for (TrieNode child : node.child.values()) { + count += countWords(child); + } + + return count; } /** - * Validates if a given word contains only lowercase alphabetic characters - * (a-z). - *

- * The method uses a regular expression to check if the word matches the pattern - * of only lowercase letters. + * Check if the prefix exists in the trie * - * @param word The word to be validated. - * @return true if the word is valid (only a-z), false otherwise. + * @param prefix the prefix to be checked in the Trie + * @return true / false depending on the prefix if exists in the Trie */ - public static boolean isValid(String word) { - return word.matches("^[a-z]+$"); + public boolean startsWithPrefix(String prefix) { + TrieNode currentNode = root; + + for (int i = 0; i < prefix.length(); i++) { + TrieNode node = currentNode.child.getOrDefault(prefix.charAt(i), null); + if (node == null) { + return false; + } + + currentNode = node; + } + + return true; + } + + /** + * Count the number of words starting with the given prefix in the trie + * + * @param prefix the prefix to be checked in the Trie + * @return count of words + */ + public int countWordsWithPrefix(String prefix) { + TrieNode currentNode = root; + + for (int i = 0; i < prefix.length(); i++) { + TrieNode node = currentNode.child.getOrDefault(prefix.charAt(i), null); + if (node == null) { + return 0; + } + + currentNode = node; + } + + return countWords(currentNode); } } diff --git a/src/main/java/com/thealgorithms/divideandconquer/CountingInversions.java b/src/main/java/com/thealgorithms/divideandconquer/CountingInversions.java new file mode 100644 index 000000000000..15c8bc33b58f --- /dev/null +++ b/src/main/java/com/thealgorithms/divideandconquer/CountingInversions.java @@ -0,0 +1,101 @@ +package com.thealgorithms.divideandconquer; + +/** + * A utility class for counting the number of inversions in an array. + *

+ * An inversion is a pair (i, j) such that i < j and arr[i] > arr[j]. + * This class implements a divide-and-conquer approach, similar to merge sort, + * to count the number of inversions efficiently. + *

+ * Time Complexity: O(n log n) + * Space Complexity: O(n) (due to temporary arrays during merge step) + * + *

Applications: + * - Used in algorithms related to sorting and permutation analysis. + * - Helps in determining how far an array is from being sorted. + * - Applicable in bioinformatics and signal processing. + * + *

This class cannot be instantiated, as it is intended to provide + * only static utility methods. + * + * @author Hardvan + */ +public final class CountingInversions { + private CountingInversions() { + } + + /** + * Counts the number of inversions in the given array. + * + * @param arr The input array of integers. + * @return The total number of inversions in the array. + */ + public static int countInversions(int[] arr) { + return mergeSortAndCount(arr, 0, arr.length - 1); + } + + /** + * Recursively divides the array into two halves, sorts them, and counts + * the number of inversions. Uses a modified merge sort approach. + * + * @param arr The input array. + * @param left The starting index of the current segment. + * @param right The ending index of the current segment. + * @return The number of inversions within the segment [left, right]. + */ + private static int mergeSortAndCount(int[] arr, int left, int right) { + if (left >= right) { + return 0; + } + + int mid = left + (right - left) / 2; + int inversions = 0; + + inversions += mergeSortAndCount(arr, left, mid); + inversions += mergeSortAndCount(arr, mid + 1, right); + inversions += mergeAndCount(arr, left, mid, right); + return inversions; + } + + /** + * Merges two sorted subarrays and counts the cross-inversions between them. + * A cross-inversion occurs when an element from the right subarray is + * smaller than an element from the left subarray. + * + * @param arr The input array. + * @param left The starting index of the first subarray. + * @param mid The ending index of the first subarray and midpoint of the segment. + * @param right The ending index of the second subarray. + * @return The number of cross-inversions between the two subarrays. + */ + private static int mergeAndCount(int[] arr, int left, int mid, int right) { + int[] leftArr = new int[mid - left + 1]; + int[] rightArr = new int[right - mid]; + + System.arraycopy(arr, left, leftArr, 0, mid - left + 1); + System.arraycopy(arr, mid + 1, rightArr, 0, right - mid); + + int i = 0; + int j = 0; + int k = left; + int inversions = 0; + + while (i < leftArr.length && j < rightArr.length) { + if (leftArr[i] <= rightArr[j]) { + arr[k++] = leftArr[i++]; + } else { + arr[k++] = rightArr[j++]; + inversions += mid + 1 - left - i; + } + } + + while (i < leftArr.length) { + arr[k++] = leftArr[i++]; + } + while (j < rightArr.length) { + arr[k++] = rightArr[j++]; + } + + return inversions; + } +} diff --git a/src/main/java/com/thealgorithms/divideandconquer/MedianOfTwoSortedArrays.java b/src/main/java/com/thealgorithms/divideandconquer/MedianOfTwoSortedArrays.java new file mode 100644 index 000000000000..d9e51442253c --- /dev/null +++ b/src/main/java/com/thealgorithms/divideandconquer/MedianOfTwoSortedArrays.java @@ -0,0 +1,53 @@ +package com.thealgorithms.divideandconquer; + +public final class MedianOfTwoSortedArrays { + + private MedianOfTwoSortedArrays() { + } + + /** + * Finds the median of two sorted arrays in logarithmic time. + * + * @param nums1 the first sorted array + * @param nums2 the second sorted array + * @return the median of the combined sorted array + * @throws IllegalArgumentException if the input arrays are not sorted + */ + public static double findMedianSortedArrays(int[] nums1, int[] nums2) { + if (nums1.length > nums2.length) { + return findMedianSortedArrays(nums2, nums1); // Ensure nums1 is the smaller array + } + + int m = nums1.length; + int n = nums2.length; + int low = 0; + int high = m; + while (low <= high) { + int partition1 = (low + high) / 2; // Partition in the first array + int partition2 = (m + n + 1) / 2 - partition1; // Partition in the second array + + int maxLeft1 = (partition1 == 0) ? Integer.MIN_VALUE : nums1[partition1 - 1]; + int minRight1 = (partition1 == m) ? Integer.MAX_VALUE : nums1[partition1]; + int maxLeft2 = (partition2 == 0) ? Integer.MIN_VALUE : nums2[partition2 - 1]; + int minRight2 = (partition2 == n) ? Integer.MAX_VALUE : nums2[partition2]; + + // Check if partition is valid + if (maxLeft1 <= minRight2 && maxLeft2 <= minRight1) { + // If combined array length is odd + if (((m + n) & 1) == 1) { + return Math.max(maxLeft1, maxLeft2); + } + // If combined array length is even + else { + return (Math.max(maxLeft1, maxLeft2) + Math.min(minRight1, minRight2)) / 2.0; + } + } else if (maxLeft1 > minRight2) { + high = partition1 - 1; + } else { + low = partition1 + 1; + } + } + + throw new IllegalArgumentException("Input arrays are not sorted"); + } +} diff --git a/src/main/java/com/thealgorithms/divideandconquer/TilingProblem.java b/src/main/java/com/thealgorithms/divideandconquer/TilingProblem.java new file mode 100644 index 000000000000..ff2e1678ab48 --- /dev/null +++ b/src/main/java/com/thealgorithms/divideandconquer/TilingProblem.java @@ -0,0 +1,99 @@ +package com.thealgorithms.divideandconquer; + +/** + * This class provides a solution to the Tiling Problem using divide-and-conquer. + *

+ * The Tiling Problem involves filling a 2^n x 2^n board with a single missing + * square using L-shaped tiles (each tile covers exactly three squares). + * The algorithm recursively divides the board into four quadrants, places an + * L-shaped tile in the appropriate quadrant, and fills the remaining areas. + * + *

Applications: + * - Used in graphics and image processing. + * - Helpful in solving puzzles and tiling problems in competitive programming. + * + * @author Hardvan + */ +public final class TilingProblem { + private TilingProblem() { + } + + /** + * A counter used to label the L-shaped tiles placed on the board. + */ + private static int tile = 1; + + /** + * A 2D array representing the board to be tiled. + */ + private static int[][] board; + + /** + * Solves the tiling problem for a 2^n x 2^n board with one missing square. + * + * @param size The size of the board (must be a power of 2). + * @param missingRow The row index of the missing square. + * @param missingCol The column index of the missing square. + * @return A 2D array representing the tiled board with L-shaped tiles. + */ + public static int[][] solveTiling(int size, int missingRow, int missingCol) { + board = new int[size][size]; + fillBoard(size, 0, 0, missingRow, missingCol); + return board; + } + + /** + * Recursively fills the board with L-shaped tiles. + * + *

The board is divided into four quadrants. Depending on the location of + * the missing square, an L-shaped tile is placed at the center of the board + * to cover three of the four quadrants. The process is then repeated for + * each quadrant until the entire board is filled. + * + * @param size The current size of the sub-board. + * @param row The starting row index of the current sub-board. + * @param col The starting column index of the current sub-board. + * @param missingRow The row index of the missing square within the board. + * @param missingCol The column index of the missing square within the board. + */ + private static void fillBoard(int size, int row, int col, int missingRow, int missingCol) { + if (size == 1) { + return; + } + + int half = size / 2; + int t = tile++; + + // Top-left quadrant + if (missingRow < row + half && missingCol < col + half) { + fillBoard(half, row, col, missingRow, missingCol); + } else { + board[row + half - 1][col + half - 1] = t; + fillBoard(half, row, col, row + half - 1, col + half - 1); + } + + // Top-right quadrant + if (missingRow < row + half && missingCol >= col + half) { + fillBoard(half, row, col + half, missingRow, missingCol); + } else { + board[row + half - 1][col + half] = t; + fillBoard(half, row, col + half, row + half - 1, col + half); + } + + // Bottom-left quadrant + if (missingRow >= row + half && missingCol < col + half) { + fillBoard(half, row + half, col, missingRow, missingCol); + } else { + board[row + half][col + half - 1] = t; + fillBoard(half, row + half, col, row + half, col + half - 1); + } + + // Bottom-right quadrant + if (missingRow >= row + half && missingCol >= col + half) { + fillBoard(half, row + half, col + half, missingRow, missingCol); + } else { + board[row + half][col + half] = t; + fillBoard(half, row + half, col + half, row + half, col + half); + } + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/Abbreviation.java b/src/main/java/com/thealgorithms/dynamicprogramming/Abbreviation.java new file mode 100644 index 000000000000..60c0fd0a3cde --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/Abbreviation.java @@ -0,0 +1,56 @@ +package com.thealgorithms.dynamicprogramming; + +/** + * A class that provides a solution to the abbreviation problem. + * + * Problem: Given two strings, `a` and `b`, determine if string `a` can be + * transformed into string `b` by performing the following operations: + * 1. Capitalize zero or more of `a`'s lowercase letters (i.e., convert them to uppercase). + * 2. Delete any of the remaining lowercase letters from `a`. + * + * The task is to determine whether it is possible to make string `a` equal to string `b`. + * + * @author Hardvan + */ +public final class Abbreviation { + private Abbreviation() { + } + + /** + * Determines if string `a` can be transformed into string `b` by capitalizing + * some of its lowercase letters and deleting the rest. + * + * @param a The input string which may contain both uppercase and lowercase letters. + * @param b The target string containing only uppercase letters. + * @return {@code true} if string `a` can be transformed into string `b`, + * {@code false} otherwise. + * + * Time Complexity: O(n * m) where n = length of string `a` and m = length of string `b`. + * Space Complexity: O(n * m) due to the dynamic programming table. + */ + public static boolean abbr(String a, String b) { + int n = a.length(); + int m = b.length(); + + boolean[][] dp = new boolean[n + 1][m + 1]; + + dp[0][0] = true; + + for (int i = 0; i < n; i++) { + for (int j = 0; j <= m; j++) { + if (dp[i][j]) { + // Case 1: If the current characters match (or can be capitalized to match) + if (j < m && Character.toUpperCase(a.charAt(i)) == b.charAt(j)) { + dp[i + 1][j + 1] = true; + } + // Case 2: If the character in `a` is lowercase, we can skip it + if (Character.isLowerCase(a.charAt(i))) { + dp[i + 1][j] = true; + } + } + } + } + + return dp[n][m]; + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/AllConstruct.java b/src/main/java/com/thealgorithms/dynamicprogramming/AllConstruct.java new file mode 100644 index 000000000000..e7712b13a2b7 --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/AllConstruct.java @@ -0,0 +1,61 @@ +package com.thealgorithms.dynamicprogramming; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class provides a solution to the "All Construct" problem. + * + * The problem is to determine all the ways a target string can be constructed + * from a given list of substrings. Each substring in the word bank can be used + * multiple times, and the order of substrings matters. + * + * @author Hardvan + */ +public final class AllConstruct { + private AllConstruct() { + } + + /** + * Finds all possible ways to construct the target string using substrings + * from the given word bank. + * Time Complexity: O(n * m * k), where n = length of the target, + * m = number of words in wordBank, and k = average length of a word. + * + * Space Complexity: O(n * m) due to the size of the table storing combinations. + * + * @param target The target string to construct. + * @param wordBank An iterable collection of substrings that can be used to construct the target. + * @return A list of lists, where each inner list represents one possible + * way of constructing the target string using the given word bank. + */ + public static List> allConstruct(String target, Iterable wordBank) { + List>> table = new ArrayList<>(target.length() + 1); + + for (int i = 0; i <= target.length(); i++) { + table.add(new ArrayList<>()); + } + + table.get(0).add(new ArrayList<>()); + + for (int i = 0; i <= target.length(); i++) { + if (!table.get(i).isEmpty()) { + for (String word : wordBank) { + if (i + word.length() <= target.length() && target.substring(i, i + word.length()).equals(word)) { + + List> newCombinations = new ArrayList<>(); + for (List combination : table.get(i)) { + List newCombination = new ArrayList<>(combination); + newCombination.add(word); + newCombinations.add(newCombination); + } + + table.get(i + word.length()).addAll(newCombinations); + } + } + } + } + + return table.get(target.length()); + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmask.java b/src/main/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmask.java new file mode 100644 index 000000000000..5a894ca004b7 --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmask.java @@ -0,0 +1,91 @@ +package com.thealgorithms.dynamicprogramming; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * The AssignmentUsingBitmask class is used to calculate the total number of ways + * tasks can be distributed among people, given specific constraints on who can perform which tasks. + * The approach uses bitmasking and dynamic programming to efficiently solve the problem. + * + * @author Hardvan + */ +public final class AssignmentUsingBitmask { + + private final int totalTasks; + private final int[][] dp; + private final List> task; + private final int finalMask; + + /** + * Constructor for the AssignmentUsingBitmask class. + * + * @param taskPerformed a list of lists, where each inner list contains the tasks that a person can perform. + * @param total the total number of tasks. + */ + public AssignmentUsingBitmask(List> taskPerformed, int total) { + this.totalTasks = total; + this.dp = new int[1 << taskPerformed.size()][total + 1]; + for (int[] row : dp) { + Arrays.fill(row, -1); + } + + this.task = new ArrayList<>(totalTasks + 1); + for (int i = 0; i <= totalTasks; i++) { + this.task.add(new ArrayList<>()); + } + + // Final mask to check if all persons are included + this.finalMask = (1 << taskPerformed.size()) - 1; + + // Fill the task list + for (int i = 0; i < taskPerformed.size(); i++) { + for (int j : taskPerformed.get(i)) { + this.task.get(j).add(i); + } + } + } + + /** + * Counts the ways to assign tasks until the given task number with the specified mask. + * + * @param mask the bitmask representing the current state of assignments. + * @param taskNo the current task number being processed. + * @return the number of ways to assign tasks. + */ + private int countWaysUntil(int mask, int taskNo) { + if (mask == finalMask) { + return 1; + } + if (taskNo > totalTasks) { + return 0; + } + if (dp[mask][taskNo] != -1) { + return dp[mask][taskNo]; + } + + int totalWays = countWaysUntil(mask, taskNo + 1); + + // Assign tasks to all possible persons + for (int p : task.get(taskNo)) { + // If the person is already assigned a task + if ((mask & (1 << p)) != 0) { + continue; + } + totalWays += countWaysUntil(mask | (1 << p), taskNo + 1); + } + + dp[mask][taskNo] = totalWays; + return dp[mask][taskNo]; + } + + /** + * Counts the total number of ways to distribute tasks among persons. + * + * @return the total number of ways to distribute tasks. + */ + public int countNoOfWays() { + return countWaysUntil(0, 1); + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/BoundaryFill.java b/src/main/java/com/thealgorithms/dynamicprogramming/BoundaryFill.java index 3fa8728930cb..8494492f293f 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/BoundaryFill.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/BoundaryFill.java @@ -52,48 +52,4 @@ public static void boundaryFill(int[][] image, int xCoordinate, int yCoordinate, boundaryFill(image, xCoordinate - 1, yCoordinate - 1, newColor, boundaryColor); } } - - /** - * This method will print the 2D image matrix - * - * @param image The image to be printed on the console - */ - public static void printImageArray(int[][] image) { - for (int i = 0; i < image.length; i++) { - for (int j = 0; j < image[0].length; j++) { - System.out.print(image[i][j] + " "); - } - - System.out.println(); - } - } - - // Driver Program - public static void main(String[] args) { - // Input 2D image matrix - int[][] image = { - {0, 0, 0, 0, 0, 0, 0}, - {0, 3, 3, 3, 3, 0, 0}, - {0, 3, 0, 0, 3, 0, 0}, - {0, 3, 0, 0, 3, 3, 3}, - {0, 3, 3, 3, 0, 0, 3}, - {0, 0, 0, 3, 0, 0, 3}, - {0, 0, 0, 3, 3, 3, 3}, - }; - - boundaryFill(image, 2, 2, 5, 3); - - /* Output ==> - * 0 0 0 0 0 0 0 - 0 3 3 3 3 0 0 - 0 3 5 5 3 0 0 - 0 3 5 5 3 3 3 - 0 3 3 3 5 5 3 - 0 0 0 3 5 5 3 - 0 0 0 3 3 3 3 - * */ - - // print 2D image matrix - printImageArray(image); - } } diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsack.java b/src/main/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsack.java index b433c44b9077..3c1851a8c46c 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsack.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsack.java @@ -1,39 +1,67 @@ package com.thealgorithms.dynamicprogramming; -/* A Naive recursive implementation -of 0-1 Knapsack problem */ +/** + * A naive recursive implementation of the 0-1 Knapsack problem. + * + *

The 0-1 Knapsack problem is a classic optimization problem where you are + * given a set of items, each with a weight and a value, and a knapsack with a + * fixed capacity. The goal is to determine the maximum value that can be + * obtained by selecting a subset of the items such that the total weight does + * not exceed the knapsack's capacity. Each item can either be included (1) or + * excluded (0), hence the name "0-1" Knapsack.

+ * + *

This class provides a brute-force recursive approach to solving the + * problem. It evaluates all possible combinations of items to find the optimal + * solution, but this approach has exponential time complexity and is not + * suitable for large input sizes.

+ * + *

Time Complexity: O(2^n), where n is the number of items.

+ * + *

Space Complexity: O(n), due to the recursive function call stack.

+ */ public final class BruteForceKnapsack { private BruteForceKnapsack() { } - // Returns the maximum value that - // can be put in a knapsack of - // capacity W + + /** + * Solves the 0-1 Knapsack problem using a recursive brute-force approach. + * + * @param w the total capacity of the knapsack + * @param wt an array where wt[i] represents the weight of the i-th item + * @param val an array where val[i] represents the value of the i-th item + * @param n the number of items available for selection + * @return the maximum value that can be obtained with the given capacity + * + *

The function uses recursion to explore all possible subsets of items. + * For each item, it has two choices: either include it in the knapsack + * (if it fits) or exclude it. It returns the maximum value obtainable + * through these two choices.

+ * + *

Base Cases: + *

    + *
  • If no items are left (n == 0), the maximum value is 0.
  • + *
  • If the knapsack's remaining capacity is 0 (w == 0), no more items can + * be included, and the value is 0.
  • + *

+ * + *

Recursive Steps: + *

    + *
  • If the weight of the n-th item exceeds the current capacity, it is + * excluded from the solution, and the function proceeds with the remaining + * items.
  • + *
  • Otherwise, the function considers two possibilities: include the n-th + * item or exclude it, and returns the maximum value of these two scenarios.
  • + *

+ */ static int knapSack(int w, int[] wt, int[] val, int n) { - // Base Case if (n == 0 || w == 0) { return 0; } - // If weight of the nth item is - // more than Knapsack capacity W, - // then this item cannot be included - // in the optimal solution if (wt[n - 1] > w) { return knapSack(w, wt, val, n - 1); - } // Return the maximum of two cases: - // (1) nth item included - // (2) not included - else { - return Math.max(val[n - 1] + knapSack(w - wt[n - 1], wt, val, n - 1), knapSack(w, wt, val, n - 1)); + } else { + return Math.max(knapSack(w, wt, val, n - 1), val[n - 1] + knapSack(w - wt[n - 1], wt, val, n - 1)); } } - - // Driver code - public static void main(String[] args) { - int[] val = new int[] {60, 100, 120}; - int[] wt = new int[] {10, 20, 30}; - int w = 50; - int n = val.length; - System.out.println(knapSack(w, wt, val, n)); - } } diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/CoinChange.java b/src/main/java/com/thealgorithms/dynamicprogramming/CoinChange.java index 12cc29faa923..7edc9603dc8b 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/CoinChange.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/CoinChange.java @@ -7,15 +7,6 @@ public final class CoinChange { private CoinChange() { } - // Driver Program - public static void main(String[] args) { - int amount = 12; - int[] coins = {2, 4, 5}; - - System.out.println("Number of combinations of getting change for " + amount + " is: " + change(coins, amount)); - System.out.println("Minimum number of coins required for amount :" + amount + " is: " + minimumCoins(coins, amount)); - } - /** * This method finds the number of combinations of getting change for a * given amount and change coins @@ -32,8 +23,6 @@ public static int change(int[] coins, int amount) { for (int i = coin; i < amount + 1; i++) { combinations[i] += combinations[i - coin]; } - // Uncomment the below line to see the state of combinations for each coin - // printAmount(combinations); } return combinations[amount]; @@ -65,16 +54,7 @@ public static int minimumCoins(int[] coins, int amount) { } } } - // Uncomment the below line to see the state of combinations for each coin - // printAmount(minimumCoins); - return minimumCoins[amount]; - } - // A basic print method which prints all the contents of the array - public static void printAmount(int[] arr) { - for (int i = 0; i < arr.length; i++) { - System.out.print(arr[i] + " "); - } - System.out.println(); + return minimumCoins[amount]; } } diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/DiceThrow.java b/src/main/java/com/thealgorithms/dynamicprogramming/DiceThrow.java index e1be3ead5895..c3dc3f32ff7c 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/DiceThrow.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/DiceThrow.java @@ -9,6 +9,7 @@ /* Hence, storing the results of the solved sub-problems saves time. And it can be done using Dynamic Programming(DP). +// Time Complexity: O(m * n * x) where m is number of faces, n is number of dice and x is given sum. Following is implementation of Dynamic Programming approach. */ // Code ----> // Java program to find number of ways to get sum 'x' with 'n' @@ -43,21 +44,4 @@ public static long findWays(int m, int n, int x) { return table[n][x]; } - - public static void main(String[] args) { - System.out.println(findWays(4, 2, 1)); - System.out.println(findWays(2, 2, 3)); - System.out.println(findWays(6, 3, 8)); - System.out.println(findWays(4, 2, 5)); - System.out.println(findWays(4, 3, 5)); - } } -/* -OUTPUT: -0 -2 -21 -4 -6 - */ -// Time Complexity: O(m * n * x) where m is number of faces, n is number of dice and x is given sum. diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java b/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java index 5855030fc65c..0d6aff2bbef3 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java @@ -2,7 +2,6 @@ import java.util.HashMap; import java.util.Map; -import java.util.Scanner; /** * @author Varun Upadhyay (https://github.com/varunu28) @@ -11,27 +10,19 @@ public final class Fibonacci { private Fibonacci() { } - private static final Map CACHE = new HashMap<>(); - - public static void main(String[] args) { - // Methods all returning [0, 1, 1, 2, 3, 5, ...] for n = [0, 1, 2, 3, 4, 5, ...] - Scanner sc = new Scanner(System.in); - int n = sc.nextInt(); - - System.out.println(fibMemo(n)); - System.out.println(fibBotUp(n)); - System.out.println(fibOptimized(n)); - System.out.println(fibBinet(n)); - sc.close(); - } + static final Map CACHE = new HashMap<>(); /** * This method finds the nth fibonacci number using memoization technique * * @param n The input n for which we have to determine the fibonacci number * Outputs the nth fibonacci number + * @throws IllegalArgumentException if n is negative */ public static int fibMemo(int n) { + if (n < 0) { + throw new IllegalArgumentException("Input n must be non-negative"); + } if (CACHE.containsKey(n)) { return CACHE.get(n); } @@ -52,8 +43,12 @@ public static int fibMemo(int n) { * * @param n The input n for which we have to determine the fibonacci number * Outputs the nth fibonacci number + * @throws IllegalArgumentException if n is negative */ public static int fibBotUp(int n) { + if (n < 0) { + throw new IllegalArgumentException("Input n must be non-negative"); + } Map fib = new HashMap<>(); for (int i = 0; i <= n; i++) { @@ -80,9 +75,13 @@ public static int fibBotUp(int n) { * Time Complexity will be O(n) *

* Whereas , the above functions will take O(n) Space. + * @throws IllegalArgumentException if n is negative * @author Shoaib Rayeen (https://github.com/shoaibrayeen) */ public static int fibOptimized(int n) { + if (n < 0) { + throw new IllegalArgumentException("Input n must be non-negative"); + } if (n == 0) { return 0; } @@ -105,9 +104,14 @@ public static int fibOptimized(int n) { * = 1.6180339887... Now, let's look at Binet's formula: Sn = Φⁿ–(– Φ⁻ⁿ)/√5 We first calculate * the squareRootof5 and phi and store them in variables. Later, we apply Binet's formula to get * the required term. Time Complexity will be O(1) + * @param n The input n for which we have to determine the fibonacci number + * Outputs the nth fibonacci number + * @throws IllegalArgumentException if n is negative */ - public static int fibBinet(int n) { + if (n < 0) { + throw new IllegalArgumentException("Input n must be non-negative"); + } double squareRootOf5 = Math.sqrt(5); double phi = (1 + squareRootOf5) / 2; return (int) ((Math.pow(phi, n) - Math.pow(-phi, -n)) / squareRootOf5); diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/KadaneAlgorithm.java b/src/main/java/com/thealgorithms/dynamicprogramming/KadaneAlgorithm.java index 905815d10a29..7a0a3da94c1e 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/KadaneAlgorithm.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/KadaneAlgorithm.java @@ -1,36 +1,52 @@ package com.thealgorithms.dynamicprogramming; /** - * @author Siddhant Swarup Mallick - * Program description - To find the maximum subarray sum + * This class implements Kadane's Algorithm to find the maximum subarray sum + * within a given array of integers. The algorithm efficiently computes the maximum + * sum of a contiguous subarray in linear time. + * + * Author: Siddhant Swarup Mallick */ public final class KadaneAlgorithm { private KadaneAlgorithm() { } /** - * OUTPUT : - * Input - {89,56,98,123,26,75,12,40,39,68,91} - * Output: it returns either true or false - * 1st approach Time Complexity : O(n) - * Auxiliary Space Complexity : O(1) + * Computes the maximum subarray sum using Kadane's Algorithm and checks + * if it matches a predicted answer. + * + * @param a The input array of integers for which the maximum + * subarray sum is to be calculated. + * @param predictedAnswer The expected maximum subarray sum to be verified + * against the computed sum. + * @return true if the computed maximum subarray sum equals the predicted + * answer, false otherwise. + * + *

Example:

+ *
+     * Input: {89, 56, 98, 123, 26, 75, 12, 40, 39, 68, 91}
+     * Output: true if the maximum subarray sum is equal to the
+     *         predicted answer.
+     * 
+ * + *

Algorithmic Complexity:

+ *
    + *
  • Time Complexity: O(n) - the algorithm iterates through the array once.
  • + *
  • Auxiliary Space Complexity: O(1) - only a constant amount of additional space is used.
  • + *
*/ public static boolean maxSum(int[] a, int predictedAnswer) { int sum = a[0]; int runningSum = 0; + for (int k : a) { - runningSum = runningSum + k; - // running sum of all the indexs are stored + runningSum += k; sum = Math.max(sum, runningSum); - // the max is stored inorder to the get the maximum sum if (runningSum < 0) { runningSum = 0; } - // if running sum is negative then it is initialized to zero } - // for-each loop is used to iterate over the array and find the maximum subarray sum + return sum == predictedAnswer; - // It returns true if sum and predicted answer matches - // The predicted answer is the answer itself. So it always return true } } diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackMemoization.java b/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackMemoization.java index 396efb1a7893..3501e302a6ef 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackMemoization.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackMemoization.java @@ -1,5 +1,7 @@ package com.thealgorithms.dynamicprogramming; +import java.util.Arrays; + /** * Recursive Solution for 0-1 knapsack with memoization * This method is basically an extension to the recursive approach so that we @@ -15,10 +17,8 @@ int knapSack(int capacity, int[] weights, int[] profits, int numOfItems) { int[][] dpTable = new int[numOfItems + 1][capacity + 1]; // Loop to initially fill the table with -1 - for (int i = 0; i < numOfItems + 1; i++) { - for (int j = 0; j < capacity + 1; j++) { - dpTable[i][j] = -1; - } + for (int[] table : dpTable) { + Arrays.fill(table, -1); } return solveKnapsackRecursive(capacity, weights, profits, numOfItems, dpTable); @@ -38,7 +38,6 @@ int solveKnapsackRecursive(int capacity, int[] weights, int[] profits, int numOf if (weights[numOfItems - 1] > capacity) { // Store the value of function call stack in table dpTable[numOfItems][capacity] = solveKnapsackRecursive(capacity, weights, profits, numOfItems - 1, dpTable); - return dpTable[numOfItems][capacity]; } else { // case 1. include the item, if it is less than the capacity final int includeCurrentItem = profits[numOfItems - 1] + solveKnapsackRecursive(capacity - weights[numOfItems - 1], weights, profits, numOfItems - 1, dpTable); @@ -48,7 +47,7 @@ int solveKnapsackRecursive(int capacity, int[] weights, int[] profits, int numOf // Store the value of function call stack in table and return dpTable[numOfItems][capacity] = Math.max(includeCurrentItem, excludeCurrentItem); - return dpTable[numOfItems][capacity]; } + return dpTable[numOfItems][capacity]; } } diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java b/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java new file mode 100644 index 000000000000..7bc0855e0566 --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java @@ -0,0 +1,75 @@ +package com.thealgorithms.dynamicprogramming; + +/** + * Implementation of the Longest Increasing Subsequence (LIS) problem using + * an O(n log n) dynamic programming solution enhanced with binary search. + * + * @author Vusal Huseynov (https://github.com/huseynovvusal) + */ +public final class LongestIncreasingSubsequenceNLogN { + private LongestIncreasingSubsequenceNLogN() { + } + + /** + * Finds the index of the smallest element in the array that is greater than + * or equal to the target using binary search. The search is restricted to + * the first `size` elements of the array. + * + * @param arr The array to search in (assumed to be sorted up to `size`). + * @param size The number of valid elements in the array. + * @param target The target value to find the lower bound for. + * @return The index of the lower bound. + */ + private static int lowerBound(int[] arr, int target, int size) { + int l = 0; + int r = size; + + while (l < r) { + int mid = l + (r - l) / 2; + + if (target > arr[mid]) { + // Move right if target is greater than mid element + l = mid + 1; + } else { + // Move left if target is less than or equal to mid element + r = mid; + } + } + + // Return the index where the target can be inserted + return l; + } + + /** + * Calculates the length of the Longest Increasing Subsequence (LIS) in the given array. + * + * @param arr The input array of integers. + * @return The length of the LIS. + */ + public static int lengthOfLIS(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; // Return 0 for empty or null arrays + } + + // tails[i] - the smallest end element of an increasing subsequence of length i+1 + int[] tails = new int[arr.length]; + // size - the length of the longest increasing subsequence found so far + int size = 0; + + for (int x : arr) { + // Find the position to replace or extend the subsequence + int index = lowerBound(tails, x, size); + + // Update the tails array with the current element + tails[index] = x; + + // If the element extends the subsequence, increase the size + if (index == size) { + size++; + } + } + + // Return the length of the LIS + return size; + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainMultiplication.java b/src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainMultiplication.java index 45568d21f295..1edb7207dee2 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainMultiplication.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainMultiplication.java @@ -2,38 +2,32 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Scanner; +/** + * The MatrixChainMultiplication class provides functionality to compute the + * optimal way to multiply a sequence of matrices. The optimal multiplication + * order is determined using dynamic programming, which minimizes the total + * number of scalar multiplications required. + */ public final class MatrixChainMultiplication { private MatrixChainMultiplication() { } - private static final Scanner SCANNER = new Scanner(System.in); - private static final ArrayList MATRICES = new ArrayList<>(); - private static int size; + // Matrices to store minimum multiplication costs and split points private static int[][] m; private static int[][] s; private static int[] p; - public static void main(String[] args) { - int count = 1; - while (true) { - String[] mSize = input("input size of matrix A(" + count + ") ( ex. 10 20 ) : "); - int col = Integer.parseInt(mSize[0]); - if (col == 0) { - break; - } - int row = Integer.parseInt(mSize[1]); - - Matrix matrix = new Matrix(count, col, row); - MATRICES.add(matrix); - count++; - } - for (Matrix m : MATRICES) { - System.out.format("A(%d) = %2d x %2d%n", m.count(), m.col(), m.row()); - } - - size = MATRICES.size(); + /** + * Calculates the optimal order for multiplying a given list of matrices. + * + * @param matrices an ArrayList of Matrix objects representing the matrices + * to be multiplied. + * @return a Result object containing the matrices of minimum costs and + * optimal splits. + */ + public static Result calculateMatrixChainOrder(ArrayList matrices) { + int size = matrices.size(); m = new int[size + 1][size + 1]; s = new int[size + 1][size + 1]; p = new int[size + 1]; @@ -44,51 +38,20 @@ public static void main(String[] args) { } for (int i = 0; i < p.length; i++) { - p[i] = i == 0 ? MATRICES.get(i).col() : MATRICES.get(i - 1).row(); + p[i] = i == 0 ? matrices.get(i).col() : matrices.get(i - 1).row(); } - matrixChainOrder(); - for (int i = 0; i < size; i++) { - System.out.print("-------"); - } - System.out.println(); - printArray(m); - for (int i = 0; i < size; i++) { - System.out.print("-------"); - } - System.out.println(); - printArray(s); - for (int i = 0; i < size; i++) { - System.out.print("-------"); - } - System.out.println(); - - System.out.println("Optimal solution : " + m[1][size]); - System.out.print("Optimal parens : "); - printOptimalParens(1, size); - } - - private static void printOptimalParens(int i, int j) { - if (i == j) { - System.out.print("A" + i); - } else { - System.out.print("("); - printOptimalParens(i, s[i][j]); - printOptimalParens(s[i][j] + 1, j); - System.out.print(")"); - } - } - - private static void printArray(int[][] array) { - for (int i = 1; i < size + 1; i++) { - for (int j = 1; j < size + 1; j++) { - System.out.printf("%7d", array[i][j]); - } - System.out.println(); - } + matrixChainOrder(size); + return new Result(m, s); } - private static void matrixChainOrder() { + /** + * A helper method that computes the minimum cost of multiplying + * the matrices using dynamic programming. + * + * @param size the number of matrices in the multiplication sequence. + */ + private static void matrixChainOrder(int size) { for (int i = 1; i < size + 1; i++) { m[i][i] = 0; } @@ -109,33 +72,92 @@ private static void matrixChainOrder() { } } - private static String[] input(String string) { - System.out.print(string); - return (SCANNER.nextLine().split(" ")); - } -} - -class Matrix { + /** + * The Result class holds the results of the matrix chain multiplication + * calculation, including the matrix of minimum costs and split points. + */ + public static class Result { + private final int[][] m; + private final int[][] s; + + /** + * Constructs a Result object with the specified matrices of minimum + * costs and split points. + * + * @param m the matrix of minimum multiplication costs. + * @param s the matrix of optimal split points. + */ + public Result(int[][] m, int[][] s) { + this.m = m; + this.s = s; + } - private final int count; - private final int col; - private final int row; + /** + * Returns the matrix of minimum multiplication costs. + * + * @return the matrix of minimum multiplication costs. + */ + public int[][] getM() { + return m; + } - Matrix(int count, int col, int row) { - this.count = count; - this.col = col; - this.row = row; + /** + * Returns the matrix of optimal split points. + * + * @return the matrix of optimal split points. + */ + public int[][] getS() { + return s; + } } - int count() { - return count; - } + /** + * The Matrix class represents a matrix with its dimensions and count. + */ + public static class Matrix { + private final int count; + private final int col; + private final int row; + + /** + * Constructs a Matrix object with the specified count, number of columns, + * and number of rows. + * + * @param count the identifier for the matrix. + * @param col the number of columns in the matrix. + * @param row the number of rows in the matrix. + */ + public Matrix(int count, int col, int row) { + this.count = count; + this.col = col; + this.row = row; + } - int col() { - return col; - } + /** + * Returns the identifier of the matrix. + * + * @return the identifier of the matrix. + */ + public int count() { + return count; + } - int row() { - return row; + /** + * Returns the number of columns in the matrix. + * + * @return the number of columns in the matrix. + */ + public int col() { + return col; + } + + /** + * Returns the number of rows in the matrix. + * + * @return the number of rows in the matrix. + */ + public int row() { + return row; + } } } diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainRecursiveTopDownMemoisation.java b/src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainRecursiveTopDownMemoisation.java index 6c1c4cf54ffc..0c1031c6805c 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainRecursiveTopDownMemoisation.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainRecursiveTopDownMemoisation.java @@ -1,15 +1,31 @@ package com.thealgorithms.dynamicprogramming; -// Matrix-chain Multiplication -// Problem Statement -// we have given a chain A1,A2,...,Ani of n matrices, where for i = 1,2,...,n, -// matrix Ai has dimension pi−1 ×pi -// , fully parenthesize the product A1A2 ···An in a way that -// minimizes the number of scalar multiplications. +/** + * The MatrixChainRecursiveTopDownMemoisation class implements the matrix-chain + * multiplication problem using a top-down recursive approach with memoization. + * + *

Given a chain of matrices A1, A2, ..., An, where matrix Ai has dimensions + * pi-1 × pi, this algorithm finds the optimal way to fully parenthesize the + * product A1A2...An in a way that minimizes the total number of scalar + * multiplications required.

+ * + *

This implementation uses a memoization technique to store the results of + * subproblems, which significantly reduces the number of recursive calls and + * improves performance compared to a naive recursive approach.

+ */ public final class MatrixChainRecursiveTopDownMemoisation { private MatrixChainRecursiveTopDownMemoisation() { } + /** + * Calculates the minimum number of scalar multiplications needed to multiply + * a chain of matrices. + * + * @param p an array of integers representing the dimensions of the matrices. + * The length of the array is n + 1, where n is the number of matrices. + * @return the minimum number of multiplications required to multiply the chain + * of matrices. + */ static int memoizedMatrixChain(int[] p) { int n = p.length; int[][] m = new int[n][n]; @@ -21,6 +37,17 @@ static int memoizedMatrixChain(int[] p) { return lookupChain(m, p, 1, n - 1); } + /** + * A recursive helper method to lookup the minimum number of multiplications + * for multiplying matrices from index i to index j. + * + * @param m the memoization table storing the results of subproblems. + * @param p an array of integers representing the dimensions of the matrices. + * @param i the starting index of the matrix chain. + * @param j the ending index of the matrix chain. + * @return the minimum number of multiplications needed to multiply matrices + * from i to j. + */ static int lookupChain(int[][] m, int[] p, int i, int j) { if (i == j) { m[i][j] = 0; @@ -38,11 +65,4 @@ static int lookupChain(int[][] m, int[] p, int i, int j) { } return m[i][j]; } - - // in this code we are taking the example of 4 matrixes whose orders are 1x2,2x3,3x4,4x5 - // respectively output should be Minimum number of multiplications is 38 - public static void main(String[] args) { - int[] arr = {1, 2, 3, 4, 5}; - System.out.println("Minimum number of multiplications is " + memoizedMatrixChain(arr)); - } } diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/MaximumSumOfNonAdjacentElements.java b/src/main/java/com/thealgorithms/dynamicprogramming/MaximumSumOfNonAdjacentElements.java new file mode 100644 index 000000000000..49af3ae3db88 --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/MaximumSumOfNonAdjacentElements.java @@ -0,0 +1,95 @@ +package com.thealgorithms.dynamicprogramming; + +/** + * Class to find the maximum sum of non-adjacent elements in an array. This + * class contains two approaches: one with O(n) space complexity and another + * with O(1) space optimization. For more information, refer to + * https://takeuforward.org/data-structure/maximum-sum-of-non-adjacent-elements-dp-5/ + */ +final class MaximumSumOfNonAdjacentElements { + + private MaximumSumOfNonAdjacentElements() { + } + + /** + * Approach 1: Uses a dynamic programming array to store the maximum sum at + * each index. Time Complexity: O(n) - where n is the length of the input + * array. Space Complexity: O(n) - due to the additional dp array. + * @param arr The input array of integers. + * @return The maximum sum of non-adjacent elements. + */ + public static int getMaxSumApproach1(int[] arr) { + if (arr.length == 0) { + return 0; // Check for empty array + } + + int n = arr.length; + int[] dp = new int[n]; + + // Base case: Maximum sum if only one element is present. + dp[0] = arr[0]; + + for (int ind = 1; ind < n; ind++) { + + // Case 1: Do not take the current element, carry forward the previous max + // sum. + int notTake = dp[ind - 1]; + + // Case 2: Take the current element, add it to the max sum up to two + // indices before. + int take = arr[ind]; + if (ind > 1) { + take += dp[ind - 2]; + } + + // Store the maximum of both choices in the dp array. + dp[ind] = Math.max(take, notTake); + } + + return dp[n - 1]; + } + + /** + * Approach 2: Optimized space complexity approach using two variables instead + * of an array. Time Complexity: O(n) - where n is the length of the input + * array. Space Complexity: O(1) - as it only uses constant space for two + * variables. + * @param arr The input array of integers. + * @return The maximum sum of non-adjacent elements. + */ + public static int getMaxSumApproach2(int[] arr) { + if (arr.length == 0) { + return 0; // Check for empty array + } + + int n = arr.length; + + // Two variables to keep track of previous two results: + // prev1 = max sum up to the last element (n-1) + // prev2 = max sum up to the element before last (n-2) + + int prev1 = arr[0]; // Base case: Maximum sum for the first element. + int prev2 = 0; + + for (int ind = 1; ind < n; ind++) { + // Case 1: Do not take the current element, keep the last max sum. + int notTake = prev1; + + // Case 2: Take the current element and add it to the result from two + // steps back. + int take = arr[ind]; + if (ind > 1) { + take += prev2; + } + + // Calculate the current maximum sum and update previous values. + int current = Math.max(take, notTake); + + // Shift prev1 and prev2 for the next iteration. + prev2 = prev1; + prev1 = current; + } + + return prev1; + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/NewManShanksPrime.java b/src/main/java/com/thealgorithms/dynamicprogramming/NewManShanksPrime.java index d15ea1f78a78..3db40148f1c2 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/NewManShanksPrime.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/NewManShanksPrime.java @@ -1,25 +1,48 @@ package com.thealgorithms.dynamicprogramming; /** + * The NewManShanksPrime class provides a method to determine whether the nth + * New Man Shanks prime matches an expected answer. + * + *

This is based on the New Man Shanks prime sequence defined by the recurrence + * relation:

+ * + *
+ * a(n) = 2 * a(n-1) + a(n-2) for n >= 2
+ * a(0) = 1
+ * a(1) = 1
+ * 
+ * + *

For more information on New Man Shanks primes, please refer to the + * + * Wikipedia article.

+ * + *

Note: The class is designed to be non-instantiable.

+ * * @author Siddhant Swarup Mallick - * Program description - To find the New Man Shanks Prime. - * Wikipedia */ public final class NewManShanksPrime { private NewManShanksPrime() { } + /** + * Calculates the nth New Man Shanks prime and checks if it equals the + * expected answer. + * + * @param n the index of the New Man Shanks prime to calculate (0-based). + * @param expectedAnswer the expected value of the nth New Man Shanks prime. + * @return true if the calculated nth New Man Shanks prime matches the + * expected answer; false otherwise. + */ public static boolean nthManShanksPrime(int n, int expectedAnswer) { int[] a = new int[n + 1]; - // array of n+1 size is initialized a[0] = 1; a[1] = 1; - // The 0th and 1st index position values are fixed. They are initialized as 1 + for (int i = 2; i <= n; i++) { a[i] = 2 * a[i - 1] + a[i - 2]; } - // The loop is continued till n + return a[n] == expectedAnswer; - // returns true if calculated answer matches with expected answer } } diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/RodCutting.java b/src/main/java/com/thealgorithms/dynamicprogramming/RodCutting.java index f56fc4ff5641..6d33826b6b17 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/RodCutting.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/RodCutting.java @@ -15,9 +15,17 @@ private RodCutting() { * @param price An array representing the prices of different pieces, where price[i-1] * represents the price of a piece of length i. * @param n The length of the rod to be cut. + * @throws IllegalArgumentException if the price array is null or empty, or if n is less than 0. * @return The maximum obtainable value. */ public static int cutRod(int[] price, int n) { + if (price == null || price.length == 0) { + throw new IllegalArgumentException("Price array cannot be null or empty."); + } + if (n < 0) { + throw new IllegalArgumentException("Rod length cannot be negative."); + } + // Create an array to store the maximum obtainable values for each rod length. int[] val = new int[n + 1]; val[0] = 0; diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSum.java b/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSum.java index 3dd41d2fdc0f..da8667afd0ce 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSum.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSum.java @@ -9,28 +9,25 @@ private SubsetSum() { * * @param arr the array containing integers. * @param sum the target sum of the subset. - * @return {@code true} if a subset exists that sums to the given value, otherwise {@code false}. + * @return {@code true} if a subset exists that sums to the given value, + * otherwise {@code false}. */ public static boolean subsetSum(int[] arr, int sum) { int n = arr.length; - boolean[][] isSum = new boolean[n + 1][sum + 1]; - // Initialize the first column to true since a sum of 0 can always be achieved with an empty subset. - for (int i = 0; i <= n; i++) { - isSum[i][0] = true; - } + // Initialize a single array to store the possible sums + boolean[] isSum = new boolean[sum + 1]; + + // Mark isSum[0] = true since a sum of 0 is always possible with 0 elements + isSum[0] = true; - // Fill the subset sum matrix - for (int i = 1; i <= n; i++) { - for (int j = 1; j <= sum; j++) { - if (arr[i - 1] <= j) { - isSum[i][j] = isSum[i - 1][j] || isSum[i - 1][j - arr[i - 1]]; - } else { - isSum[i][j] = isSum[i - 1][j]; - } + // Iterate through each Element in the array + for (int i = 0; i < n; i++) { + // Traverse the isSum array backwards to prevent overwriting values + for (int j = sum; j >= arr[i]; j--) { + isSum[j] = isSum[j] || isSum[j - arr[i]]; } } - - return isSum[n][sum]; + return isSum[sum]; } } diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimized.java b/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimized.java new file mode 100644 index 000000000000..946da46cb292 --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimized.java @@ -0,0 +1,35 @@ +package com.thealgorithms.dynamicprogramming; +/* +The Sum of Subset problem determines whether a subset of elements from a +given array sums up to a specific target value. +*/ +public final class SubsetSumSpaceOptimized { + private SubsetSumSpaceOptimized() { + } + /** + * This method checks whether the subset of an array + * contains a given sum or not. This is an space + * optimized solution using 1D boolean array + * Time Complexity: O(n * sum), Space complexity: O(sum) + * + * @param arr An array containing integers + * @param sum The target sum of the subset + * @return True or False + */ + public static boolean isSubsetSum(int[] arr, int sum) { + int n = arr.length; + // Declare the boolean array with size sum + 1 + boolean[] dp = new boolean[sum + 1]; + + // Initialize the first element as true + dp[0] = true; + + // Find the subset sum using 1D array + for (int i = 0; i < n; i++) { + for (int j = sum; j >= arr[i]; j--) { + dp[j] = dp[j] || dp[j - arr[i]]; + } + } + return dp[sum]; + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/TreeMatching.java b/src/main/java/com/thealgorithms/dynamicprogramming/TreeMatching.java new file mode 100644 index 000000000000..9fd82ccaf078 --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/TreeMatching.java @@ -0,0 +1,78 @@ +package com.thealgorithms.dynamicprogramming; + +import com.thealgorithms.datastructures.graphs.UndirectedAdjacencyListGraph; + +/** + * This class implements the algorithm for calculating the maximum weighted matching in a tree. + * The tree is represented as an undirected graph with weighted edges. + * + * Problem Description: + * Given an undirected tree G = (V, E) with edge weights γ: E → N and a root r ∈ V, + * the goal is to find a maximum weight matching M ⊆ E such that no two edges in M + * share a common vertex. The sum of the weights of the edges in M, ∑ e∈M γ(e), should be maximized. + * For more Information: Matching (graph theory) + * + * @author Deniz Altunkapan + */ +public class TreeMatching { + + private UndirectedAdjacencyListGraph graph; + private int[][] dp; + + /** + * Constructor that initializes the graph and the DP table. + * + * @param graph The graph that represents the tree and is used for the matching algorithm. + */ + public TreeMatching(UndirectedAdjacencyListGraph graph) { + this.graph = graph; + this.dp = new int[graph.size()][2]; + } + + /** + * Calculates the maximum weighted matching for the tree, starting from the given root node. + * + * @param root The index of the root node of the tree. + * @param parent The index of the parent node (used for recursion). + * @return The maximum weighted matching for the tree, starting from the root node. + * + */ + public int getMaxMatching(int root, int parent) { + if (root < 0 || root >= graph.size()) { + throw new IllegalArgumentException("Invalid root: " + root); + } + maxMatching(root, parent); + return Math.max(dp[root][0], dp[root][1]); + } + + /** + * Recursively computes the maximum weighted matching for a node, assuming that the node + * can either be included or excluded from the matching. + * + * @param node The index of the current node for which the matching is calculated. + * @param parent The index of the parent node (to avoid revisiting the parent node during recursion). + */ + private void maxMatching(int node, int parent) { + dp[node][0] = 0; + dp[node][1] = 0; + + int sumWithoutEdge = 0; + for (int adjNode : graph.getNeighbors(node)) { + if (adjNode == parent) { + continue; + } + maxMatching(adjNode, node); + sumWithoutEdge += Math.max(dp[adjNode][0], dp[adjNode][1]); + } + + dp[node][0] = sumWithoutEdge; + + for (int adjNode : graph.getNeighbors(node)) { + if (adjNode == parent) { + continue; + } + int weight = graph.getEdgeWeight(node, adjNode); + dp[node][1] = Math.max(dp[node][1], sumWithoutEdge - Math.max(dp[adjNode][0], dp[adjNode][1]) + dp[adjNode][0] + weight); + } + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCount.java b/src/main/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCount.java new file mode 100755 index 000000000000..8c7ea6179e3f --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCount.java @@ -0,0 +1,98 @@ +package com.thealgorithms.dynamicprogramming; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * Utility class to find the number of unique subsequences that can be + * produced from a given string. + * + *

This class contains static methods to compute the unique subsequence count + * using dynamic programming and recursion. It ensures that duplicate characters + * are not counted multiple times in the subsequences.

+ * + *

Author: https://github.com/Tuhinm2002

+ */ +public final class UniqueSubsequencesCount { + + /** + * Private constructor to prevent instantiation of this utility class. + * This class should only be used in a static context. + * + * @throws UnsupportedOperationException if attempted to instantiate. + */ + private UniqueSubsequencesCount() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * Finds the number of unique subsequences that can be generated from + * the given string. + * + *

This method initializes a dynamic programming (DP) array and invokes + * the recursive helper function to compute the subsequence count.

+ * + * @param str the input string from which subsequences are generated + * @return the total count of unique subsequences + */ + public static int countSubseq(String str) { + + // DP array initialized to store intermediate results + int[] dp = new int[str.length() + 1]; + Arrays.fill(dp, -1); + + // Calls the recursive function to compute the result + return countSubsequences(str, 0, dp); + } + + /** + * Recursive helper function to count the number of unique subsequences + * starting from the given index. + * + *

Uses a HashSet to avoid counting duplicate characters within + * a single subsequence.

+ * + * @param st the input string + * @param idx the current index from which to calculate subsequences + * @param dp dynamic programming array used to memoize results + * @return the total number of unique subsequences starting from the + * current index + */ + public static int countSubsequences(String st, int idx, int[] dp) { + + // Base case: when index exceeds the string length + if (idx >= st.length()) { + return 0; + } + + // If result is already calculated, return the memoized value + if (dp[idx] != -1) { + return dp[idx]; + } + + // Set to store characters to avoid duplicates + Set set = new HashSet<>(); + + int res = 0; + + // Iterate over the string starting from current index + for (int j = idx; j < st.length(); j++) { + + // If character is already in the set, skip it + if (set.contains(st.charAt(j))) { + continue; + } + + // Add character to set and recursively calculate subsequences + set.add(st.charAt(j)); + + // 1 for the current subsequence + recursive call for the rest of the string + res = 1 + countSubsequences(st, j + 1, dp) + res; + } + + // Memoize the result + dp[idx] = res; + return dp[idx]; + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java b/src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java index 0f5359f4d95e..022b43184a88 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java @@ -1,21 +1,40 @@ package com.thealgorithms.dynamicprogramming; /** - * Imagine you have a collection of N wines placed next to each other on the - * shelf. The price of ith wine is pi(Prices of different wines are different). - * Because wine gets better every year supposing today is year 1, on year y the - * price would be y*pi i.e y times the value of the initial year. You want to - * sell all wines but you have to sell one wine per year. One more constraint on - * each year you are allowed to sell either leftmost or rightmost wine on the - * shelf. You are not allowed to reorder. You have to find the maximum profit + * The WineProblem class provides a solution to the wine selling problem. + * Given a collection of N wines with different prices, the objective is to maximize profit by selling + * one wine each year, considering the constraint that only the leftmost or rightmost wine can be sold + * at any given time. * + * The price of the ith wine is pi, and the selling price increases by a factor of the year in which + * it is sold. This class implements three approaches to solve the problem: + * + * 1. **Recursion**: A straightforward recursive method that computes the maximum profit. + * - Time Complexity: O(2^N) + * - Space Complexity: O(N) due to recursive calls. + * + * 2. **Top-Down Dynamic Programming (Memoization)**: This approach caches the results of subproblems + * to avoid redundant computations. + * - Time Complexity: O(N^2) + * - Space Complexity: O(N^2) for the storage of results and O(N) for recursion stack. + * + * 3. **Bottom-Up Dynamic Programming (Tabulation)**: This method builds a table iteratively to + * compute the maximum profit for all possible subproblems. + * - Time Complexity: O(N^2) + * - Space Complexity: O(N^2) for the table. */ public final class WineProblem { private WineProblem() { } - // Method 1: Using Recursion - // Time Complexity=0(2^N) Space Complexity=Recursion extra space + /** + * Calculate maximum profit using recursion. + * + * @param arr Array of wine prices. + * @param si Start index of the wine to consider. + * @param ei End index of the wine to consider. + * @return Maximum profit obtainable by selling the wines. + */ public static int wpRecursion(int[] arr, int si, int ei) { int n = arr.length; int year = (n - (ei - si + 1)) + 1; @@ -29,8 +48,15 @@ public static int wpRecursion(int[] arr, int si, int ei) { return Math.max(start, end); } - // Method 2: Top-Down DP(Memoization) - // Time Complexity=0(N*N) Space Complexity=0(N*N)+Recursion extra space + /** + * Calculate maximum profit using top-down dynamic programming with memoization. + * + * @param arr Array of wine prices. + * @param si Start index of the wine to consider. + * @param ei End index of the wine to consider. + * @param strg 2D array to store results of subproblems. + * @return Maximum profit obtainable by selling the wines. + */ public static int wptd(int[] arr, int si, int ei, int[][] strg) { int n = arr.length; int year = (n - (ei - si + 1)) + 1; @@ -45,15 +71,22 @@ public static int wptd(int[] arr, int si, int ei, int[][] strg) { int end = wptd(arr, si, ei - 1, strg) + arr[ei] * year; int ans = Math.max(start, end); - strg[si][ei] = ans; return ans; } - // Method 3: Bottom-Up DP(Tabulation) - // Time Complexity=0(N*N/2)->0(N*N) Space Complexity=0(N*N) + /** + * Calculate maximum profit using bottom-up dynamic programming with tabulation. + * + * @param arr Array of wine prices. + * @throws IllegalArgumentException if the input array is null or empty. + * @return Maximum profit obtainable by selling the wines. + */ public static int wpbu(int[] arr) { + if (arr == null || arr.length == 0) { + throw new IllegalArgumentException("Input array cannot be null or empty."); + } int n = arr.length; int[][] strg = new int[n][n]; @@ -73,13 +106,4 @@ public static int wpbu(int[] arr) { } return strg[0][n - 1]; } - - public static void main(String[] args) { - int[] arr = {2, 3, 5, 1, 4}; - System.out.println("Method 1: " + wpRecursion(arr, 0, arr.length - 1)); - System.out.println("Method 2: " + wptd(arr, 0, arr.length - 1, new int[arr.length][arr.length])); - System.out.println("Method 3: " + wpbu(arr)); - } } -// Memoization vs Tabulation : https://www.geeksforgeeks.org/tabulation-vs-memoization/ -// Question Link : https://www.geeksforgeeks.org/maximum-profit-sale-wines/ diff --git a/src/main/java/com/thealgorithms/geometry/BresenhamLine.java b/src/main/java/com/thealgorithms/geometry/BresenhamLine.java new file mode 100644 index 000000000000..51d9930c0250 --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/BresenhamLine.java @@ -0,0 +1,69 @@ +package com.thealgorithms.geometry; + +import java.awt.Point; +import java.util.ArrayList; +import java.util.List; + +/** + * The {@code BresenhamLine} class implements the Bresenham's line algorithm, + * which is an efficient way to determine the points of a straight line + * between two given points in a 2D space. + * + *

This algorithm uses integer arithmetic to calculate the points, + * making it suitable for rasterization in computer graphics.

+ * + * For more information, please visit {@link https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm} + */ +public final class BresenhamLine { + + private BresenhamLine() { + // Private constructor to prevent instantiation. + } + + /** + * Finds the list of points that form a straight line between two endpoints. + * + * @param x0 the x-coordinate of the starting point + * @param y0 the y-coordinate of the starting point + * @param x1 the x-coordinate of the ending point + * @param y1 the y-coordinate of the ending point + * @return a {@code List} containing all points on the line + */ + public static List findLine(int x0, int y0, int x1, int y1) { + List line = new ArrayList<>(); + + // Calculate differences and steps for each axis + int dx = Math.abs(x1 - x0); // Change in x + int dy = Math.abs(y1 - y0); // Change in y + int sx = (x0 < x1) ? 1 : -1; // Step in x direction + int sy = (y0 < y1) ? 1 : -1; // Step in y direction + int err = dx - dy; // Initial error term + + // Loop until we reach the endpoint + while (true) { + line.add(new Point(x0, y0)); // Add current point to the line + + // Check if we've reached the endpoint + if (x0 == x1 && y0 == y1) { + break; // Exit loop if endpoint is reached + } + + // Calculate error term doubled for decision making + final int e2 = err * 2; + + // Adjust x coordinate if necessary + if (e2 > -dy) { + err -= dy; // Update error term + x0 += sx; // Move to next point in x direction + } + + // Adjust y coordinate if necessary + if (e2 < dx) { + err += dx; // Update error term + y0 += sy; // Move to next point in y direction + } + } + + return line; // Return the list of points forming the line + } +} diff --git a/src/main/java/com/thealgorithms/geometry/ConvexHull.java b/src/main/java/com/thealgorithms/geometry/ConvexHull.java new file mode 100644 index 000000000000..17f400854c64 --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/ConvexHull.java @@ -0,0 +1,117 @@ +package com.thealgorithms.geometry; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +/** + * A class providing algorithms to compute the convex hull of a set of points + * using brute-force and recursive methods. + * + * Convex hull: The smallest convex polygon that contains all the given points. + * + * Algorithms provided: + * 1. Brute-Force Method + * 2. Recursive (Divide-and-Conquer) Method + * + * @author Hardvan + */ +public final class ConvexHull { + private ConvexHull() { + } + + private static boolean checkPointOrientation(Point i, Point j, Point k) { + int detK = Point.orientation(i, j, k); + if (detK > 0) { + return true; // pointsLeftOfIJ + } else if (detK < 0) { + return false; // pointsRightOfIJ + } else { + return k.compareTo(i) >= 0 && k.compareTo(j) <= 0; + } + } + + public static List convexHullBruteForce(List points) { + Set convexSet = new TreeSet<>(Comparator.naturalOrder()); + + for (int i = 0; i < points.size() - 1; i++) { + for (int j = i + 1; j < points.size(); j++) { + boolean allPointsOnOneSide = true; + boolean leftSide = checkPointOrientation(points.get(i), points.get(j), points.get((i + 1) % points.size())); + + for (int k = 0; k < points.size(); k++) { + if (k != i && k != j && checkPointOrientation(points.get(i), points.get(j), points.get(k)) != leftSide) { + allPointsOnOneSide = false; + break; + } + } + + if (allPointsOnOneSide) { + convexSet.add(points.get(i)); + convexSet.add(points.get(j)); + } + } + } + + return new ArrayList<>(convexSet); + } + + public static List convexHullRecursive(List points) { + Collections.sort(points); + Set convexSet = new HashSet<>(); + Point leftMostPoint = points.get(0); + Point rightMostPoint = points.get(points.size() - 1); + + convexSet.add(leftMostPoint); + convexSet.add(rightMostPoint); + + List upperHull = new ArrayList<>(); + List lowerHull = new ArrayList<>(); + + for (int i = 1; i < points.size() - 1; i++) { + int det = Point.orientation(leftMostPoint, rightMostPoint, points.get(i)); + if (det > 0) { + upperHull.add(points.get(i)); + } else if (det < 0) { + lowerHull.add(points.get(i)); + } + } + + constructHull(upperHull, leftMostPoint, rightMostPoint, convexSet); + constructHull(lowerHull, rightMostPoint, leftMostPoint, convexSet); + + List result = new ArrayList<>(convexSet); + Collections.sort(result); + return result; + } + + private static void constructHull(Collection points, Point left, Point right, Set convexSet) { + if (!points.isEmpty()) { + Point extremePoint = null; + int extremePointDistance = Integer.MIN_VALUE; + List candidatePoints = new ArrayList<>(); + + for (Point p : points) { + int det = Point.orientation(left, right, p); + if (det > 0) { + candidatePoints.add(p); + if (det > extremePointDistance) { + extremePointDistance = det; + extremePoint = p; + } + } + } + + if (extremePoint != null) { + constructHull(candidatePoints, left, extremePoint, convexSet); + convexSet.add(extremePoint); + constructHull(candidatePoints, extremePoint, right, convexSet); + } + } + } +} diff --git a/src/main/java/com/thealgorithms/geometry/GrahamScan.java b/src/main/java/com/thealgorithms/geometry/GrahamScan.java index 1a36137895e0..1a373cf315a2 100644 --- a/src/main/java/com/thealgorithms/geometry/GrahamScan.java +++ b/src/main/java/com/thealgorithms/geometry/GrahamScan.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; import java.util.Stack; /** @@ -66,93 +65,4 @@ public GrahamScan(Point[] points) { public Iterable hull() { return new ArrayList<>(hull); } - - public record Point(int x, int y) implements Comparable { - - /** - * Default constructor - * @param x x-coordinate - * @param y y-coordinate - */ - public Point { - } - - /** - * @return the x-coordinate - */ - @Override - public int x() { - return x; - } - - /** - * @return the y-coordinate - */ - @Override - public int y() { - return y; - } - - /** - * Determines the orientation of the triplet (a, b, c). - * - * @param a The first point - * @param b The second point - * @param c The third point - * @return -1 if (a, b, c) is clockwise, 0 if collinear, +1 if counterclockwise - */ - public static int orientation(Point a, Point b, Point c) { - int val = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); - return Integer.compare(val, 0); - } - - /** - * Compares this point with another point. - * - * @param p2 The point to compare to - * @return A positive integer if this point is greater, a negative integer if less, or 0 if equal - */ - @Override - public int compareTo(Point p2) { - int cmpY = Integer.compare(this.y, p2.y); - return cmpY != 0 ? cmpY : Integer.compare(this.x, p2.x); - } - - /** - * Returns a comparator to sort points by their polar order relative to this point. - * - * @return A polar order comparator - */ - public Comparator polarOrder() { - return new PolarOrder(); - } - - private final class PolarOrder implements Comparator { - @Override - public int compare(Point p1, Point p2) { - int dx1 = p1.x - x; - int dy1 = p1.y - y; - int dx2 = p2.x - x; - int dy2 = p2.y - y; - - if (dy1 >= 0 && dy2 < 0) { - return -1; // p1 above p2 - } else if (dy2 >= 0 && dy1 < 0) { - return 1; // p1 below p2 - } else if (dy1 == 0 && dy2 == 0) { // Collinear and horizontal - return Integer.compare(dx2, dx1); - } else { - return -orientation(Point.this, p1, p2); // Compare orientation - } - } - } - - /** - * @return A string representation of this point in the format (x, y) - */ - @Override - public String toString() { - return String.format("(%d, %d)", x, y); - } - } } diff --git a/src/main/java/com/thealgorithms/geometry/MidpointCircle.java b/src/main/java/com/thealgorithms/geometry/MidpointCircle.java new file mode 100644 index 000000000000..803e8bb42b53 --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/MidpointCircle.java @@ -0,0 +1,85 @@ +package com.thealgorithms.geometry; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Class to represent the Midpoint Circle Algorithm. + * This algorithm calculates points on the circumference of a circle + * using integer arithmetic for efficient computation. + */ +public final class MidpointCircle { + + private MidpointCircle() { + // Private Constructor to prevent instantiation. + } + + /** + * Generates points on the circumference of a circle using the midpoint circle algorithm. + * + * @param centerX The x-coordinate of the circle's center. + * @param centerY The y-coordinate of the circle's center. + * @param radius The radius of the circle. + * @return A list of points on the circle, each represented as an int[] with 2 elements [x, y]. + */ + public static List generateCirclePoints(int centerX, int centerY, int radius) { + List points = new ArrayList<>(); + + // Special case for radius 0, only the center point should be added. + if (radius == 0) { + points.add(new int[] {centerX, centerY}); + return points; + } + + // Start at (radius, 0) + int x = radius; + int y = 0; + + // Decision parameter + int p = 1 - radius; + + // Add the initial points in all octants + addSymmetricPoints(points, centerX, centerY, x, y); + + // Iterate while x > y + while (x > y) { + y++; + + if (p <= 0) { + // Midpoint is inside or on the circle + p = p + 2 * y + 1; + } else { + // Midpoint is outside the circle + x--; + p = p + 2 * y - 2 * x + 1; + } + + // Add points for this (x, y) + addSymmetricPoints(points, centerX, centerY, x, y); + } + + return points; + } + + /** + * Adds the symmetric points in all octants of the circle based on the current x and y values. + * + * @param points The list to which symmetric points will be added. + * @param centerX The x-coordinate of the circle's center. + * @param centerY The y-coordinate of the circle's center. + * @param x The current x-coordinate on the circumference. + * @param y The current y-coordinate on the circumference. + */ + private static void addSymmetricPoints(Collection points, int centerX, int centerY, int x, int y) { + // Octant symmetry points + points.add(new int[] {centerX + x, centerY + y}); + points.add(new int[] {centerX - x, centerY + y}); + points.add(new int[] {centerX + x, centerY - y}); + points.add(new int[] {centerX - x, centerY - y}); + points.add(new int[] {centerX + y, centerY + x}); + points.add(new int[] {centerX - y, centerY + x}); + points.add(new int[] {centerX + y, centerY - x}); + points.add(new int[] {centerX - y, centerY - x}); + } +} diff --git a/src/main/java/com/thealgorithms/geometry/MidpointEllipse.java b/src/main/java/com/thealgorithms/geometry/MidpointEllipse.java new file mode 100644 index 000000000000..fbd53c679960 --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/MidpointEllipse.java @@ -0,0 +1,131 @@ +package com.thealgorithms.geometry; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * The MidpointEllipse class implements the Midpoint Ellipse Drawing Algorithm. + * This algorithm efficiently computes the points on an ellipse by dividing it into two regions + * and using decision parameters to determine the next point to plot. + */ +public final class MidpointEllipse { + + private MidpointEllipse() { + // Private constructor to prevent instantiation + } + + /** + * Draws an ellipse using the Midpoint Ellipse Algorithm. + * + * @param centerX the x-coordinate of the center of the ellipse + * @param centerY the y-coordinate of the center of the ellipse + * @param a the length of the semi-major axis (horizontal radius) + * @param b the length of the semi-minor axis (vertical radius) + * @return a list of points (represented as int arrays) that form the ellipse + */ + public static List drawEllipse(int centerX, int centerY, int a, int b) { + List points = new ArrayList<>(); + + // Handle degenerate cases with early returns + if (a == 0 && b == 0) { + points.add(new int[] {centerX, centerY}); // Only the center point + return points; + } + + if (a == 0) { + // Semi-major axis is zero, create a vertical line + for (int y = centerY - b; y <= centerY + b; y++) { + points.add(new int[] {centerX, y}); + } + return points; // Early return + } + + if (b == 0) { + // Semi-minor axis is zero, create a horizontal line + for (int x = centerX - a; x <= centerX + a; x++) { + points.add(new int[] {x, centerY}); + } + return points; // Early return + } + + // Normal case: Non-degenerate ellipse + computeEllipsePoints(points, centerX, centerY, a, b); + + return points; // Return all calculated points of the ellipse + } + + /** + * Computes points of a non-degenerate ellipse using the Midpoint Ellipse Algorithm. + * + * @param points the list to which points will be added + * @param centerX the x-coordinate of the center of the ellipse + * @param centerY the y-coordinate of the center of the ellipse + * @param a the length of the semi-major axis (horizontal radius) + * @param b the length of the semi-minor axis (vertical radius) + */ + private static void computeEllipsePoints(Collection points, int centerX, int centerY, int a, int b) { + int x = 0; // Initial x-coordinate + int y = b; // Initial y-coordinate + + // Region 1: Initial decision parameter + double d1 = (b * b) - (a * a * b) + (0.25 * a * a); // Decision variable for region 1 + double dx = 2.0 * b * b * x; // Change in x + double dy = 2.0 * a * a * y; // Change in y + + // Region 1: When the slope is less than 1 + while (dx < dy) { + addEllipsePoints(points, centerX, centerY, x, y); + + // Update decision parameter and variables + if (d1 < 0) { + x++; + dx += (2 * b * b); // Update x change + d1 += dx + (b * b); // Update decision parameter + } else { + x++; + y--; + dx += (2 * b * b); // Update x change + dy -= (2 * a * a); // Update y change + d1 += dx - dy + (b * b); // Update decision parameter + } + } + + // Region 2: Initial decision parameter for the second region + double d2 = b * b * (x + 0.5) * (x + 0.5) + a * a * (y - 1) * (y - 1) - a * a * b * b; + + // Region 2: When the slope is greater than or equal to 1 + while (y >= 0) { + addEllipsePoints(points, centerX, centerY, x, y); + + // Update decision parameter and variables + if (d2 > 0) { + y--; + dy -= (2 * a * a); // Update y change + d2 += (a * a) - dy; // Update decision parameter + } else { + y--; + x++; + dx += (2 * b * b); // Update x change + dy -= (2 * a * a); // Update y change + d2 += dx - dy + (a * a); // Update decision parameter + } + } + } + + /** + * Adds points for all four quadrants of the ellipse based on symmetry. + * + * @param points the list to which points will be added + * @param centerX the x-coordinate of the center of the ellipse + * @param centerY the y-coordinate of the center of the ellipse + * @param x the x-coordinate relative to the center + * @param y the y-coordinate relative to the center + */ + private static void addEllipsePoints(Collection points, int centerX, int centerY, int x, int y) { + points.add(new int[] {centerX + x, centerY + y}); + points.add(new int[] {centerX - x, centerY + y}); + points.add(new int[] {centerX + x, centerY - y}); + points.add(new int[] {centerX - x, centerY - y}); + } +} diff --git a/src/main/java/com/thealgorithms/geometry/Point.java b/src/main/java/com/thealgorithms/geometry/Point.java new file mode 100644 index 000000000000..564edb4ba7b6 --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/Point.java @@ -0,0 +1,45 @@ +package com.thealgorithms.geometry; + +import java.util.Comparator; + +public record Point(int x, int y) implements Comparable { + + @Override + public int compareTo(Point other) { + int cmpY = Integer.compare(this.y, other.y); + return cmpY != 0 ? cmpY : Integer.compare(this.x, other.x); + } + + @Override + public String toString() { + return String.format("(%d, %d)", x, y); + } + + public Comparator polarOrder() { + return new PolarOrder(); + } + + public static int orientation(Point a, Point b, Point c) { + return Integer.compare((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x), 0); + } + + private final class PolarOrder implements Comparator { + @Override + public int compare(Point p1, Point p2) { + int dx1 = p1.x - x; + int dy1 = p1.y - y; + int dx2 = p2.x - x; + int dy2 = p2.y - y; + + if (dy1 >= 0 && dy2 < 0) { + return -1; // p1 above p2 + } else if (dy2 >= 0 && dy1 < 0) { + return 1; // p1 below p2 + } else if (dy1 == 0 && dy2 == 0) { // Collinear and horizontal + return Integer.compare(dx2, dx1); + } else { + return -orientation(Point.this, p1, p2); // Compare orientation + } + } + } +} diff --git a/src/main/java/com/thealgorithms/graph/ConstrainedShortestPath.java b/src/main/java/com/thealgorithms/graph/ConstrainedShortestPath.java new file mode 100644 index 000000000000..f397989911d9 --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/ConstrainedShortestPath.java @@ -0,0 +1,123 @@ +package com.thealgorithms.graph; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * This class implements a solution for the Constrained Shortest Path Problem (CSPP). + * also known as Shortest Path Problem with Resource Constraints (SPPRC). + * The goal is to find the shortest path between two nodes while ensuring that + * the resource constraint is not exceeded. + * + * @author Deniz Altunkapan + */ +public class ConstrainedShortestPath { + + /** + * Represents a graph using an adjacency list. + * This graph is designed for the Constrained Shortest Path Problem (CSPP). + */ + public static class Graph { + + private List> adjacencyList; + + public Graph(int numNodes) { + adjacencyList = new ArrayList<>(); + for (int i = 0; i < numNodes; i++) { + adjacencyList.add(new ArrayList<>()); + } + } + + /** + * Adds an edge to the graph. + * @param from the starting node + * @param to the ending node + * @param cost the cost of the edge + * @param resource the resource required to traverse the edge + */ + public void addEdge(int from, int to, int cost, int resource) { + adjacencyList.get(from).add(new Edge(from, to, cost, resource)); + } + + /** + * Gets the edges that are adjacent to a given node. + * @param node the node to get the edges for + * @return the list of edges adjacent to the node + */ + public List getEdges(int node) { + return adjacencyList.get(node); + } + + /** + * Gets the number of nodes in the graph. + * @return the number of nodes + */ + public int getNumNodes() { + return adjacencyList.size(); + } + + public record Edge(int from, int to, int cost, int resource) { + } + } + + private Graph graph; + private int maxResource; + + /** + * Constructs a CSPSolver with the given graph and maximum resource constraint. + * + * @param graph the graph representing the problem + * @param maxResource the maximum allowable resource + */ + public ConstrainedShortestPath(Graph graph, int maxResource) { + this.graph = graph; + this.maxResource = maxResource; + } + + /** + * Solves the CSP to find the shortest path from the start node to the target node + * without exceeding the resource constraint. + * + * @param start the starting node + * @param target the target node + * @return the minimum cost to reach the target node within the resource constraint, + * or -1 if no valid path exists + */ + public int solve(int start, int target) { + int numNodes = graph.getNumNodes(); + int[][] dp = new int[maxResource + 1][numNodes]; + + // Initialize dp table with maximum values + for (int i = 0; i <= maxResource; i++) { + Arrays.fill(dp[i], Integer.MAX_VALUE); + } + dp[0][start] = 0; + + // Dynamic Programming: Iterate over resources and nodes + for (int r = 0; r <= maxResource; r++) { + for (int u = 0; u < numNodes; u++) { + if (dp[r][u] == Integer.MAX_VALUE) { + continue; + } + for (Graph.Edge edge : graph.getEdges(u)) { + int v = edge.to(); + int cost = edge.cost(); + int resource = edge.resource(); + + if (r + resource <= maxResource) { + dp[r + resource][v] = Math.min(dp[r + resource][v], dp[r][u] + cost); + } + } + } + } + + // Find the minimum cost to reach the target node + int minCost = Integer.MAX_VALUE; + for (int r = 0; r <= maxResource; r++) { + minCost = Math.min(minCost, dp[r][target]); + } + + return minCost == Integer.MAX_VALUE ? -1 : minCost; + } +} diff --git a/src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java b/src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java new file mode 100644 index 000000000000..ba75b2f4b1b8 --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java @@ -0,0 +1,86 @@ +package com.thealgorithms.graph; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Stack; + +/** + * Finds the strongly connected components in a directed graph. + * + * @param adjList The adjacency list representation of the graph. + * @param n The number of nodes in the graph. + * @return The number of strongly connected components. + */ +public class StronglyConnectedComponentOptimized { + + public void btrack(HashMap> adjList, int[] visited, Stack dfsCallsNodes, int currentNode) { + visited[currentNode] = 1; + List neighbors = adjList.get(currentNode); + // Check for null before iterating + if (neighbors != null) { + for (int neighbor : neighbors) { + if (visited[neighbor] == -1) { + btrack(adjList, visited, dfsCallsNodes, neighbor); + } + } + } + dfsCallsNodes.add(currentNode); + } + + public void btrack2(HashMap> adjRevList, int[] visited, int currentNode, List newScc) { + visited[currentNode] = 1; + newScc.add(currentNode); + List neighbors = adjRevList.get(currentNode); + // Check for null before iterating + if (neighbors != null) { + for (int neighbor : neighbors) { + if (visited[neighbor] == -1) { + btrack2(adjRevList, visited, neighbor, newScc); + } + } + } + } + + public int getOutput(HashMap> adjList, int n) { + int[] visited = new int[n]; + Arrays.fill(visited, -1); + Stack dfsCallsNodes = new Stack<>(); + + for (int i = 0; i < n; i++) { + if (visited[i] == -1) { + btrack(adjList, visited, dfsCallsNodes, i); + } + } + + HashMap> adjRevList = new HashMap<>(); + for (int i = 0; i < n; i++) { + adjRevList.put(i, new ArrayList<>()); + } + + for (int i = 0; i < n; i++) { + List neighbors = adjList.get(i); + // Check for null before iterating + if (neighbors != null) { + for (int neighbor : neighbors) { + adjRevList.get(neighbor).add(i); + } + } + } + + Arrays.fill(visited, -1); + int stronglyConnectedComponents = 0; + + while (!dfsCallsNodes.isEmpty()) { + int node = dfsCallsNodes.pop(); + if (visited[node] == -1) { + List newScc = new ArrayList<>(); + btrack2(adjRevList, visited, node, newScc); + stronglyConnectedComponents++; + } + } + + return stronglyConnectedComponents; + } +} diff --git a/src/main/java/com/thealgorithms/graph/TravelingSalesman.java b/src/main/java/com/thealgorithms/graph/TravelingSalesman.java new file mode 100644 index 000000000000..14bf89f57cf3 --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/TravelingSalesman.java @@ -0,0 +1,155 @@ +package com.thealgorithms.graph; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * This class provides solutions to the Traveling Salesman Problem (TSP) using both brute-force and dynamic programming approaches. + * For more information, see Wikipedia. + * @author Deniz Altunkapan + */ + +public final class TravelingSalesman { + + // Private constructor to prevent instantiation + private TravelingSalesman() { + } + + /** + * Solves the Traveling Salesman Problem (TSP) using brute-force approach. + * This method generates all possible permutations of cities, calculates the total distance for each route, and returns the shortest distance found. + * + * @param distanceMatrix A square matrix where element [i][j] represents the distance from city i to city j. + * @return The shortest possible route distance visiting all cities exactly once and returning to the starting city. + */ + public static int bruteForce(int[][] distanceMatrix) { + if (distanceMatrix.length <= 1) { + return 0; + } + + List cities = new ArrayList<>(); + for (int i = 1; i < distanceMatrix.length; i++) { + cities.add(i); + } + + List> permutations = generatePermutations(cities); + int minDistance = Integer.MAX_VALUE; + + for (List permutation : permutations) { + List route = new ArrayList<>(); + route.add(0); + route.addAll(permutation); + int currentDistance = calculateDistance(distanceMatrix, route); + if (currentDistance < minDistance) { + minDistance = currentDistance; + } + } + + return minDistance; + } + + /** + * Computes the total distance of a given route. + * + * @param distanceMatrix A square matrix where element [i][j] represents the + * distance from city i to city j. + * @param route A list representing the order in which the cities are visited. + * @return The total distance of the route, or Integer.MAX_VALUE if the route is invalid. + */ + public static int calculateDistance(int[][] distanceMatrix, List route) { + int distance = 0; + for (int i = 0; i < route.size() - 1; i++) { + int d = distanceMatrix[route.get(i)][route.get(i + 1)]; + if (d == Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + distance += d; + } + int returnDist = distanceMatrix[route.get(route.size() - 1)][route.get(0)]; + return (returnDist == Integer.MAX_VALUE) ? Integer.MAX_VALUE : distance + returnDist; + } + + /** + * Generates all permutations of a given list of cities. + * + * @param cities A list of cities to permute. + * @return A list of all possible permutations. + */ + private static List> generatePermutations(List cities) { + List> permutations = new ArrayList<>(); + permute(cities, 0, permutations); + return permutations; + } + + /** + * Recursively generates permutations using backtracking. + * + * @param arr The list of cities. + * @param k The current index in the permutation process. + * @param output The list to store generated permutations. + */ + private static void permute(List arr, int k, List> output) { + if (k == arr.size()) { + output.add(new ArrayList<>(arr)); + return; + } + for (int i = k; i < arr.size(); i++) { + Collections.swap(arr, i, k); + permute(arr, k + 1, output); + Collections.swap(arr, i, k); + } + } + + /** + * Solves the Traveling Salesman Problem (TSP) using dynamic programming with the Held-Karp algorithm. + * + * @param distanceMatrix A square matrix where element [i][j] represents the distance from city i to city j. + * @return The shortest possible route distance visiting all cities exactly once and returning to the starting city. + * @throws IllegalArgumentException if the input matrix is not square. + */ + public static int dynamicProgramming(int[][] distanceMatrix) { + if (distanceMatrix.length == 0) { + return 0; + } + int n = distanceMatrix.length; + + for (int[] row : distanceMatrix) { + if (row.length != n) { + throw new IllegalArgumentException("Matrix must be square"); + } + } + + int[][] dp = new int[n][1 << n]; + for (int[] row : dp) { + Arrays.fill(row, Integer.MAX_VALUE); + } + dp[0][1] = 0; + + for (int mask = 1; mask < (1 << n); mask++) { + for (int u = 0; u < n; u++) { + if ((mask & (1 << u)) == 0 || dp[u][mask] == Integer.MAX_VALUE) { + continue; + } + for (int v = 0; v < n; v++) { + if ((mask & (1 << v)) != 0 || distanceMatrix[u][v] == Integer.MAX_VALUE) { + continue; + } + int newMask = mask | (1 << v); + dp[v][newMask] = Math.min(dp[v][newMask], dp[u][mask] + distanceMatrix[u][v]); + } + } + } + + int minDistance = Integer.MAX_VALUE; + int fullMask = (1 << n) - 1; + for (int i = 1; i < n; i++) { + if (dp[i][fullMask] != Integer.MAX_VALUE && distanceMatrix[i][0] != Integer.MAX_VALUE) { + minDistance = Math.min(minDistance, dp[i][fullMask] + distanceMatrix[i][0]); + } + } + + return minDistance == Integer.MAX_VALUE ? 0 : minDistance; + } +} diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/BandwidthAllocation.java b/src/main/java/com/thealgorithms/greedyalgorithms/BandwidthAllocation.java new file mode 100644 index 000000000000..602cdcd5ad40 --- /dev/null +++ b/src/main/java/com/thealgorithms/greedyalgorithms/BandwidthAllocation.java @@ -0,0 +1,58 @@ +package com.thealgorithms.greedyalgorithms; + +import java.util.Arrays; + +/** + * Class to solve the Bandwidth Allocation Problem. + * The goal is to maximize the value gained by allocating bandwidth to users. + * Example: + * Bandwidth = 10 + * Users = [3, 5, 7] + * Values = [10, 20, 30] + * The maximum value achievable is 40 by allocating 3 units to user 0 and 7 units to user 2. + * + * @author Hardvan + */ +public final class BandwidthAllocation { + private BandwidthAllocation() { + } + + /** + * Allocates bandwidth to maximize value. + * Steps: + * 1. Calculate the ratio of value/demand for each user. + * 2. Sort the users in descending order of the ratio. + * 3. Allocate bandwidth to users in order of the sorted list. + * 4. If the bandwidth is not enough to allocate the full demand of a user, allocate a fraction of the demand. + * 5. Return the maximum value achievable. + * + * @param bandwidth total available bandwidth to allocate + * @param users array of user demands + * @param values array of values associated with each user's demand + * @return the maximum value achievable + */ + public static int maxValue(int bandwidth, int[] users, int[] values) { + int n = users.length; + double[][] ratio = new double[n][2]; // {index, ratio} + + for (int i = 0; i < n; i++) { + ratio[i][0] = i; + ratio[i][1] = (double) values[i] / users[i]; + } + + Arrays.sort(ratio, (a, b) -> Double.compare(b[1], a[1])); + + int maxValue = 0; + for (int i = 0; i < n; i++) { + int index = (int) ratio[i][0]; + if (bandwidth >= users[index]) { + maxValue += values[index]; + bandwidth -= users[index]; + } else { + maxValue += (int) (ratio[i][1] * bandwidth); + break; + } + } + return maxValue; + } +} diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/EgyptianFraction.java b/src/main/java/com/thealgorithms/greedyalgorithms/EgyptianFraction.java new file mode 100644 index 000000000000..35cbfe876b05 --- /dev/null +++ b/src/main/java/com/thealgorithms/greedyalgorithms/EgyptianFraction.java @@ -0,0 +1,35 @@ +package com.thealgorithms.greedyalgorithms; + +import java.util.ArrayList; +import java.util.List; + +/** + * Class to represent a fraction as a sum of unique unit fractions. + * Example: + * 2/3 = 1/2 + 1/6 + * 3/10 = 1/4 + 1/20 + * + * @author Hardvan + */ +public final class EgyptianFraction { + private EgyptianFraction() { + } + + /** + * Calculates the Egyptian Fraction representation of a given fraction. + * + * @param numerator the numerator of the fraction + * @param denominator the denominator of the fraction + * @return List of unit fractions represented as strings "1/x" + */ + public static List getEgyptianFraction(int numerator, int denominator) { + List result = new ArrayList<>(); + while (numerator != 0) { + int x = (int) Math.ceil((double) denominator / numerator); + result.add("1/" + x); + numerator = numerator * x - denominator; + denominator = denominator * x; + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/FractionalKnapsack.java b/src/main/java/com/thealgorithms/greedyalgorithms/FractionalKnapsack.java index 082bd9c68b32..9535a7c6190e 100644 --- a/src/main/java/com/thealgorithms/greedyalgorithms/FractionalKnapsack.java +++ b/src/main/java/com/thealgorithms/greedyalgorithms/FractionalKnapsack.java @@ -3,39 +3,50 @@ import java.util.Arrays; import java.util.Comparator; -// Problem Link: https://en.wikipedia.org/wiki/Continuous_knapsack_problem - +/** + * The FractionalKnapsack class provides a method to solve the fractional knapsack problem + * using a greedy algorithm approach. It allows for selecting fractions of items to maximize + * the total value in a knapsack with a given weight capacity. + * + * The problem consists of a set of items, each with a weight and a value, and a knapsack + * that can carry a maximum weight. The goal is to maximize the value of items in the knapsack, + * allowing for the inclusion of fractions of items. + * + * Problem Link: https://en.wikipedia.org/wiki/Continuous_knapsack_problem + */ public final class FractionalKnapsack { private FractionalKnapsack() { } - // Function to perform fractional knapsack + + /** + * Computes the maximum value that can be accommodated in a knapsack of a given capacity. + * + * @param weight an array of integers representing the weights of the items + * @param value an array of integers representing the values of the items + * @param capacity an integer representing the maximum weight capacity of the knapsack + * @return the maximum value that can be obtained by including the items in the knapsack + */ public static int fractionalKnapsack(int[] weight, int[] value, int capacity) { - // Create a 2D array to store item indices and their value-to-weight ratios. double[][] ratio = new double[weight.length][2]; - // Populate the ratio array with item indices and their value-to-weight ratios. for (int i = 0; i < weight.length; i++) { - ratio[i][0] = i; // Assign item index. - ratio[i][1] = value[i] / (double) weight[i]; // Calculate and assign value-to-weight ratio. + ratio[i][0] = i; + ratio[i][1] = value[i] / (double) weight[i]; } - // Sort items by their value-to-weight ratios in descending order. Arrays.sort(ratio, Comparator.comparingDouble(o -> o[1])); - int finalValue = 0; // Variable to store the final knapsack value. - double current = capacity; // Variable to track the remaining capacity of the knapsack. + int finalValue = 0; + double current = capacity; - // Iterate through the sorted items to select items for the knapsack. for (int i = ratio.length - 1; i >= 0; i--) { - int index = (int) ratio[i][0]; // Get the item index. + int index = (int) ratio[i][0]; if (current >= weight[index]) { - // If the entire item can fit in the knapsack, add its value. finalValue += value[index]; current -= weight[index]; } else { - // If only a fraction of the item can fit, add a proportionate value. finalValue += (int) (ratio[i][1] * current); - break; // Stop adding items to the knapsack since it's full. + break; } } return finalValue; diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/KCenters.java b/src/main/java/com/thealgorithms/greedyalgorithms/KCenters.java new file mode 100644 index 000000000000..152b36053345 --- /dev/null +++ b/src/main/java/com/thealgorithms/greedyalgorithms/KCenters.java @@ -0,0 +1,62 @@ +package com.thealgorithms.greedyalgorithms; + +import java.util.Arrays; + +/** + * Given a set of points and a number k. + * The goal is to minimize the maximum distance between any point and its nearest center. + * Each point is assigned to the nearest center. + * The distance between two points is the Euclidean distance. + * The problem is NP-hard. + * + * @author Hardvan + */ +public final class KCenters { + private KCenters() { + } + + /** + * Finds the maximum distance to the nearest center given k centers. + * Steps: + * 1. Initialize an array {@code selected} of size n and an array {@code maxDist} of size n. + * 2. Set the first node as selected and update the maxDist array. + * 3. For each center, find the farthest node from the selected centers. + * 4. Update the maxDist array. + * 5. Return the maximum distance to the nearest center. + * + * @param distances matrix representing distances between nodes + * @param k the number of centers + * @return the maximum distance to the nearest center + */ + public static int findKCenters(int[][] distances, int k) { + int n = distances.length; + boolean[] selected = new boolean[n]; + int[] maxDist = new int[n]; + + Arrays.fill(maxDist, Integer.MAX_VALUE); + + selected[0] = true; + for (int i = 1; i < n; i++) { + maxDist[i] = Math.min(maxDist[i], distances[0][i]); + } + + for (int centers = 1; centers < k; centers++) { + int farthest = -1; + for (int i = 0; i < n; i++) { + if (!selected[i] && (farthest == -1 || maxDist[i] > maxDist[farthest])) { + farthest = i; + } + } + selected[farthest] = true; + for (int i = 0; i < n; i++) { + maxDist[i] = Math.min(maxDist[i], distances[farthest][i]); + } + } + + int result = 0; + for (int dist : maxDist) { + result = Math.max(result, dist); + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTime.java b/src/main/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTime.java new file mode 100644 index 000000000000..2341bcdee9f7 --- /dev/null +++ b/src/main/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTime.java @@ -0,0 +1,37 @@ +package com.thealgorithms.greedyalgorithms; + +import java.util.Arrays; + +/** + * The MinimumWaitingTime class provides a method to calculate the minimum + * waiting time for a list of queries using a greedy algorithm. + * + * @author Hardvan + */ +public final class MinimumWaitingTime { + private MinimumWaitingTime() { + } + + /** + * Calculates the minimum waiting time for a list of queries. + * The function sorts the queries in non-decreasing order and then calculates + * the waiting time for each query based on its position in the sorted list. + * + * @param queries an array of integers representing the query times in picoseconds + * @return the minimum waiting time in picoseconds + */ + public static int minimumWaitingTime(int[] queries) { + int n = queries.length; + if (n <= 1) { + return 0; + } + + Arrays.sort(queries); + + int totalWaitingTime = 0; + for (int i = 0; i < n; i++) { + totalWaitingTime += queries[i] * (n - i - 1); + } + return totalWaitingTime; + } +} diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/OptimalFileMerging.java b/src/main/java/com/thealgorithms/greedyalgorithms/OptimalFileMerging.java new file mode 100644 index 000000000000..7a1f2edf8180 --- /dev/null +++ b/src/main/java/com/thealgorithms/greedyalgorithms/OptimalFileMerging.java @@ -0,0 +1,52 @@ +package com.thealgorithms.greedyalgorithms; + +import java.util.PriorityQueue; + +/** + * Class to solve the Optimal File Merging Problem. + * The goal is to minimize the cost of merging files, where the cost of merging two files is the sum of their sizes. + * The cost of merging all files is the sum of the costs of merging each pair of files. + * Example: + * files = [4, 3, 2, 6] + * The minimum cost to merge all files is 29. + * Steps: + * 1. Merge files 2 and 3 (cost = 2 + 3 = 5). New files = [4, 5, 6] + * 2. Merge files 4 and 5 (cost = 4 + 5 = 9). New files = [6, 9] + * 3. Merge files 6 and 9 (cost = 6 + 9 = 15). New files = [15] + * Total cost = 5 + 9 + 15 = 29 + * + * @author Hardvan + */ +public final class OptimalFileMerging { + private OptimalFileMerging() { + } + + /** + * Calculates the minimum cost to merge all files. + * Steps: + * 1. Add all files to a min heap. + * 2. Remove the two smallest files from the heap, merge them, and add the result back to the heap. + * 3. Repeat step 2 until there is only one file left in the heap. + * 4. The total cost is the sum of all the costs of merging the files. + * + * @param files array of file sizes + * @return the minimum cost to merge the files + */ + public static int minMergeCost(int[] files) { + PriorityQueue minHeap = new PriorityQueue<>(); + for (int file : files) { + minHeap.add(file); + } + + int totalCost = 0; + while (minHeap.size() > 1) { + int first = minHeap.poll(); + int second = minHeap.poll(); + int cost = first + second; + totalCost += cost; + + minHeap.add(cost); + } + return totalCost; + } +} diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/StockProfitCalculator.java b/src/main/java/com/thealgorithms/greedyalgorithms/StockProfitCalculator.java new file mode 100644 index 000000000000..01950d902b4f --- /dev/null +++ b/src/main/java/com/thealgorithms/greedyalgorithms/StockProfitCalculator.java @@ -0,0 +1,34 @@ +package com.thealgorithms.greedyalgorithms; + +/** + * The StockProfitCalculator class provides a method to calculate the maximum profit + * that can be made from a single buy and sell of one share of stock. + * The approach uses a greedy algorithm to efficiently determine the maximum profit. + * + * @author Hardvan + */ +public final class StockProfitCalculator { + private StockProfitCalculator() { + } + + /** + * Calculates the maximum profit from a list of stock prices. + * + * @param prices an array of integers representing the stock prices on different days + * @return the maximum profit that can be achieved from a single buy and sell + * transaction, or 0 if no profit can be made + */ + public static int maxProfit(int[] prices) { + if (prices == null || prices.length == 0) { + return 0; + } + + int minPrice = prices[0]; + int maxProfit = 0; + for (int price : prices) { + minPrice = Math.min(price, minPrice); + maxProfit = Math.max(price - minPrice, maxProfit); + } + return maxProfit; + } +} diff --git a/src/main/java/com/thealgorithms/maths/AbsoluteMax.java b/src/main/java/com/thealgorithms/maths/AbsoluteMax.java index d0c3db3790a3..c32a408b6609 100644 --- a/src/main/java/com/thealgorithms/maths/AbsoluteMax.java +++ b/src/main/java/com/thealgorithms/maths/AbsoluteMax.java @@ -17,7 +17,7 @@ public static int getMaxValue(int... numbers) { } int absMax = numbers[0]; for (int i = 1; i < numbers.length; i++) { - if (Math.abs(numbers[i]) > Math.abs(absMax)) { + if (Math.abs(numbers[i]) > Math.abs(absMax) || (Math.abs(numbers[i]) == Math.abs(absMax) && numbers[i] > absMax)) { absMax = numbers[i]; } } diff --git a/src/main/java/com/thealgorithms/maths/AbsoluteMin.java b/src/main/java/com/thealgorithms/maths/AbsoluteMin.java index 1ffe6d2e81bc..1b9575a330dd 100644 --- a/src/main/java/com/thealgorithms/maths/AbsoluteMin.java +++ b/src/main/java/com/thealgorithms/maths/AbsoluteMin.java @@ -19,7 +19,7 @@ public static int getMinValue(int... numbers) { 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 = number); + 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; } diff --git a/src/main/java/com/thealgorithms/maths/AliquotSum.java b/src/main/java/com/thealgorithms/maths/AliquotSum.java index 0dbc58bed605..996843b56826 100644 --- a/src/main/java/com/thealgorithms/maths/AliquotSum.java +++ b/src/main/java/com/thealgorithms/maths/AliquotSum.java @@ -56,7 +56,7 @@ public static int getAliquotSum(int n) { // if n is a perfect square then its root was added twice in above loop, so subtracting root // from sum if (root == (int) root) { - sum -= root; + sum -= (int) root; } return sum; } diff --git a/src/main/java/com/thealgorithms/maths/Armstrong.java b/src/main/java/com/thealgorithms/maths/Armstrong.java index ff4ae027a0b7..9a7a014ec99f 100644 --- a/src/main/java/com/thealgorithms/maths/Armstrong.java +++ b/src/main/java/com/thealgorithms/maths/Armstrong.java @@ -10,6 +10,7 @@ * An Armstrong number is often called a Narcissistic number. * * @author satyabarghav + * @modifier rahul katteda - (13/01/2025) - [updated the logic for getting total number of digits] */ public class Armstrong { @@ -20,14 +21,16 @@ public class Armstrong { * @return {@code true} if the given number is an Armstrong number, {@code false} otherwise */ public boolean isArmstrong(int number) { + if (number < 0) { + return false; // Negative numbers cannot be Armstrong numbers + } long sum = 0; - String temp = Integer.toString(number); // Convert the given number to a string - int power = temp.length(); // Extract the length of the number (number of digits) + int totalDigits = (int) Math.log10(number) + 1; // get the length of the number (number of digits) long originalNumber = number; while (originalNumber > 0) { long digit = originalNumber % 10; - sum += (long) Math.pow(digit, power); // The digit raised to the power of the number of digits and added to the sum. + sum += (long) Math.pow(digit, totalDigits); // The digit raised to the power of total number of digits and added to the sum. originalNumber /= 10; } diff --git a/src/main/java/com/thealgorithms/maths/Average.java b/src/main/java/com/thealgorithms/maths/Average.java index 6b9c20162da1..a550a7f6504d 100644 --- a/src/main/java/com/thealgorithms/maths/Average.java +++ b/src/main/java/com/thealgorithms/maths/Average.java @@ -37,7 +37,7 @@ public static double average(double[] numbers) { * @return the average of the given numbers * @throws IllegalArgumentException if the input array is {@code null} or empty */ - public static double average(int[] numbers) { + public static long average(int[] numbers) { if (numbers == null || numbers.length == 0) { throw new IllegalArgumentException("Numbers array cannot be empty or null"); } @@ -45,6 +45,6 @@ public static double average(int[] numbers) { for (int number : numbers) { sum += number; } - return (double) (sum / numbers.length); + return sum / numbers.length; } } diff --git a/src/main/java/com/thealgorithms/maths/CatalanNumbers.java b/src/main/java/com/thealgorithms/maths/CatalanNumbers.java new file mode 100644 index 000000000000..387756bdfa0c --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/CatalanNumbers.java @@ -0,0 +1,39 @@ +package com.thealgorithms.maths; + +/** + * Calculate Catalan Numbers + */ +public final class CatalanNumbers { + private CatalanNumbers() { + } + + /** + * Calculate the nth Catalan number using a recursive formula. + * + * @param n the index of the Catalan number to compute + * @return the nth Catalan number + */ + public static long catalan(final int n) { + if (n < 0) { + throw new IllegalArgumentException("Index must be non-negative"); + } + return factorial(2 * n) / (factorial(n + 1) * factorial(n)); + } + + /** + * Calculate the factorial of a number. + * + * @param n the number to compute the factorial for + * @return the factorial of n + */ + private static long factorial(final int n) { + if (n == 0 || n == 1) { + return 1; + } + long result = 1; + for (int i = 2; i <= n; i++) { + result *= i; + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/maths/ChineseRemainderTheorem.java b/src/main/java/com/thealgorithms/maths/ChineseRemainderTheorem.java new file mode 100644 index 000000000000..c26e67cffb59 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/ChineseRemainderTheorem.java @@ -0,0 +1,84 @@ +package com.thealgorithms.maths; + +import java.util.List; + +/** + * @brief Implementation of the Chinese Remainder Theorem (CRT) algorithm + * @details + * The Chinese Remainder Theorem (CRT) is used to solve systems of + * simultaneous congruences. Given several pairwise coprime moduli + * and corresponding remainders, the algorithm finds the smallest + * positive solution. + */ +public final class ChineseRemainderTheorem { + private ChineseRemainderTheorem() { + } + + /** + * @brief Solves the Chinese Remainder Theorem problem. + * @param remainders The list of remainders. + * @param moduli The list of pairwise coprime moduli. + * @return The smallest positive solution that satisfies all the given congruences. + */ + public static int solveCRT(List remainders, List moduli) { + int product = 1; + int result = 0; + + // Calculate the product of all moduli + for (int mod : moduli) { + product *= mod; + } + + // Apply the formula for each congruence + for (int i = 0; i < moduli.size(); i++) { + int partialProduct = product / moduli.get(i); + int inverse = modInverse(partialProduct, moduli.get(i)); + result += remainders.get(i) * partialProduct * inverse; + } + + // Adjust result to be the smallest positive solution + result = result % product; + if (result < 0) { + result += product; + } + + return result; + } + + /** + * @brief Computes the modular inverse of a number with respect to a modulus using + * the Extended Euclidean Algorithm. + * @param a The number for which to find the inverse. + * @param m The modulus. + * @return The modular inverse of a modulo m. + */ + private static int modInverse(int a, int m) { + int m0 = m; + int x0 = 0; + int x1 = 1; + + if (m == 1) { + return 0; + } + + while (a > 1) { + int q = a / m; + int t = m; + + // m is remainder now, process same as Euclid's algorithm + m = a % m; + a = t; + t = x0; + + x0 = x1 - q * x0; + x1 = t; + } + + // Make x1 positive + if (x1 < 0) { + x1 += m0; + } + + return x1; + } +} diff --git a/src/main/java/com/thealgorithms/maths/CircularConvolutionFFT.java b/src/main/java/com/thealgorithms/maths/CircularConvolutionFFT.java index f7010acf452d..87fc5af57b8d 100644 --- a/src/main/java/com/thealgorithms/maths/CircularConvolutionFFT.java +++ b/src/main/java/com/thealgorithms/maths/CircularConvolutionFFT.java @@ -1,6 +1,7 @@ package com.thealgorithms.maths; import java.util.ArrayList; +import java.util.Collection; /** * Class for circular convolution of two discrete signals using the convolution @@ -19,7 +20,7 @@ private CircularConvolutionFFT() { * @param x The signal to be padded. * @param newSize The new size of the signal. */ - private static void padding(ArrayList x, int newSize) { + private static void padding(Collection x, int newSize) { if (x.size() < newSize) { int diff = newSize - x.size(); for (int i = 0; i < diff; i++) { diff --git a/src/main/java/com/thealgorithms/maths/ConvolutionFFT.java b/src/main/java/com/thealgorithms/maths/ConvolutionFFT.java index ce35b02ca13b..ed1ba1bbefc3 100644 --- a/src/main/java/com/thealgorithms/maths/ConvolutionFFT.java +++ b/src/main/java/com/thealgorithms/maths/ConvolutionFFT.java @@ -1,6 +1,7 @@ package com.thealgorithms.maths; import java.util.ArrayList; +import java.util.Collection; /** * Class for linear convolution of two discrete signals using the convolution @@ -19,7 +20,7 @@ private ConvolutionFFT() { * @param x The signal to be padded. * @param newSize The new size of the signal. */ - private static void padding(ArrayList x, int newSize) { + private static void padding(Collection x, int newSize) { if (x.size() < newSize) { int diff = newSize - x.size(); for (int i = 0; i < diff; i++) { diff --git a/src/main/java/com/thealgorithms/maths/FFT.java b/src/main/java/com/thealgorithms/maths/FFT.java index 7ca7543d7985..91754bd1a80b 100644 --- a/src/main/java/com/thealgorithms/maths/FFT.java +++ b/src/main/java/com/thealgorithms/maths/FFT.java @@ -1,6 +1,7 @@ package com.thealgorithms.maths; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; /** @@ -164,6 +165,14 @@ public Complex divide(double n) { temp.img = this.img / n; return temp; } + + public double real() { + return real; + } + + public double imaginary() { + return img; + } } /** @@ -274,7 +283,7 @@ private static int reverseBits(int num, int log2n) { * * @param x The ArrayList to be padded. */ - private static void paddingPowerOfTwo(ArrayList x) { + private static void paddingPowerOfTwo(Collection x) { int n = 1; int oldSize = x.size(); while (n < oldSize) { diff --git a/src/main/java/com/thealgorithms/maths/FFTBluestein.java b/src/main/java/com/thealgorithms/maths/FFTBluestein.java index 388de6fed3eb..7a03c20cc642 100644 --- a/src/main/java/com/thealgorithms/maths/FFTBluestein.java +++ b/src/main/java/com/thealgorithms/maths/FFTBluestein.java @@ -1,6 +1,7 @@ package com.thealgorithms.maths; import java.util.ArrayList; +import java.util.List; /** * Class for calculating the Fast Fourier Transform (FFT) of a discrete signal @@ -25,7 +26,7 @@ private FFTBluestein() { * IFFT of signal x. * @param inverse True if you want to find the inverse FFT. */ - public static void fftBluestein(ArrayList x, boolean inverse) { + public static void fftBluestein(List x, boolean inverse) { int n = x.size(); int bnSize = 2 * n - 1; int direction = inverse ? -1 : 1; diff --git a/src/main/java/com/thealgorithms/maths/FastExponentiation.java b/src/main/java/com/thealgorithms/maths/FastExponentiation.java new file mode 100644 index 000000000000..27f49e27ff30 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/FastExponentiation.java @@ -0,0 +1,67 @@ +package com.thealgorithms.maths; + +/** + * This class provides a method to perform fast exponentiation (exponentiation by squaring), + * which calculates (base^exp) % mod efficiently. + * + *

The algorithm works by repeatedly squaring the base and reducing the exponent + * by half at each step. It exploits the fact that: + *

    + *
  • If exp is even, (base^exp) = (base^(exp/2))^2
  • + *
  • If exp is odd, (base^exp) = base * (base^(exp-1))
  • + *
+ * The result is computed modulo `mod` at each step to avoid overflow and keep the result within bounds. + *

+ * + *

Time complexity: O(log(exp)) — much faster than naive exponentiation (O(exp)).

+ * + * For more information, please visit {@link https://en.wikipedia.org/wiki/Exponentiation_by_squaring} + */ +public final class FastExponentiation { + + /** + * Private constructor to hide the implicit public one. + */ + private FastExponentiation() { + } + + /** + * Performs fast exponentiation to calculate (base^exp) % mod using the method + * of exponentiation by squaring. + * + *

This method efficiently computes the result by squaring the base and halving + * the exponent at each step. It multiplies the base to the result when the exponent is odd. + * + * @param base the base number to be raised to the power of exp + * @param exp the exponent to which the base is raised + * @param mod the modulus to ensure the result does not overflow + * @return (base^exp) % mod + * @throws IllegalArgumentException if the modulus is less than or equal to 0 + * @throws ArithmeticException if the exponent is negative (not supported in this implementation) + */ + public static long fastExponentiation(long base, long exp, long mod) { + if (mod <= 0) { + throw new IllegalArgumentException("Modulus must be positive."); + } + + if (exp < 0) { + throw new ArithmeticException("Negative exponent is not supported."); + } + + long result = 1; + base = base % mod; // Take the modulus of the base to handle large base values + + // Fast exponentiation by squaring algorithm + while (exp > 0) { + // If exp is odd, multiply the base to the result + if ((exp & 1) == 1) { // exp & 1 checks if exp is odd + result = result * base % mod; + } + // Square the base and halve the exponent + base = base * base % mod; // base^2 % mod to avoid overflow + exp >>= 1; // Right shift exp to divide it by 2 + } + + return result; + } +} diff --git a/src/main/java/com/thealgorithms/maths/FindKthNumber.java b/src/main/java/com/thealgorithms/maths/FindKthNumber.java index a9b267677eac..138d4b952be9 100644 --- a/src/main/java/com/thealgorithms/maths/FindKthNumber.java +++ b/src/main/java/com/thealgorithms/maths/FindKthNumber.java @@ -1,5 +1,7 @@ package com.thealgorithms.maths; +import java.util.Collections; +import java.util.PriorityQueue; import java.util.Random; /** @@ -62,4 +64,19 @@ private static void swap(int[] array, int i, int j) { array[i] = array[j]; array[j] = temp; } + + public static int findKthMaxUsingHeap(int[] array, int k) { + if (k <= 0 || k > array.length) { + throw new IllegalArgumentException("k must be between 1 and the size of the array"); + } + PriorityQueue maxHeap = new PriorityQueue<>(Collections.reverseOrder()); // using max-heap to store numbers. + for (int num : array) { + maxHeap.add(num); + } + while (k > 1) { + maxHeap.poll(); // removing max number from heap + k--; + } + return maxHeap.peek(); + } } diff --git a/src/main/java/com/thealgorithms/maths/GCD.java b/src/main/java/com/thealgorithms/maths/GCD.java index 5156e4ac881d..df27516367b2 100644 --- a/src/main/java/com/thealgorithms/maths/GCD.java +++ b/src/main/java/com/thealgorithms/maths/GCD.java @@ -1,9 +1,23 @@ package com.thealgorithms.maths; /** - * This is Euclid's algorithm, used to find the greatest common - * denominator Override function name gcd + * This class provides methods to compute the Greatest Common Divisor (GCD) of two or more integers. * + * The Greatest Common Divisor (GCD) of two or more integers is the largest positive integer that divides each of the integers without leaving a remainder. + * + * The GCD can be computed using the Euclidean algorithm, which is based on the principle that the GCD of two numbers also divides their difference. + * + * For more information, refer to the + * Greatest Common Divisor Wikipedia page. + * + * Example usage: + *

+ * int result1 = GCD.gcd(48, 18);
+ * System.out.println("GCD of 48 and 18: " + result1); // Output: 6
+ *
+ * int result2 = GCD.gcd(48, 18, 30);
+ * System.out.println("GCD of 48, 18, and 30: " + result2); // Output: 6
+ * 
* @author Oskar Enmalm 3/10/17 */ public final class GCD { @@ -40,7 +54,7 @@ public static int gcd(int num1, int num2) { * @param numbers the input array * @return gcd of all of the numbers in the input array */ - public static int gcd(int[] numbers) { + public static int gcd(int... numbers) { int result = 0; for (final var number : numbers) { result = gcd(result, number); @@ -48,12 +62,4 @@ public static int gcd(int[] numbers) { return result; } - - public static void main(String[] args) { - int[] myIntArray = {4, 16, 32}; - - // call gcd function (input array) - System.out.println(gcd(myIntArray)); // => 4 - System.out.printf("gcd(40,24)=%d gcd(24,40)=%d%n", gcd(40, 24), gcd(24, 40)); // => 8 - } } diff --git a/src/main/java/com/thealgorithms/maths/Gaussian.java b/src/main/java/com/thealgorithms/maths/Gaussian.java index 255a84d13854..1e02579757cc 100644 --- a/src/main/java/com/thealgorithms/maths/Gaussian.java +++ b/src/main/java/com/thealgorithms/maths/Gaussian.java @@ -1,12 +1,13 @@ package com.thealgorithms.maths; import java.util.ArrayList; +import java.util.List; public final class Gaussian { private Gaussian() { } - public static ArrayList gaussian(int matSize, ArrayList matrix) { + public static ArrayList gaussian(int matSize, List matrix) { int i; int j = 0; diff --git a/src/main/java/com/thealgorithms/maths/GoldbachConjecture.java b/src/main/java/com/thealgorithms/maths/GoldbachConjecture.java new file mode 100644 index 000000000000..4e962722ba88 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/GoldbachConjecture.java @@ -0,0 +1,30 @@ +package com.thealgorithms.maths; + +import static com.thealgorithms.maths.Prime.PrimeCheck.isPrime; + +/** + * This is a representation of the unsolved problem of Goldbach's Projection, according to which every + * even natural number greater than 2 can be written as the sum of 2 prime numbers + * More info: https://en.wikipedia.org/wiki/Goldbach%27s_conjecture + * @author Vasilis Sarantidis (https://github.com/BILLSARAN) + */ + +public final class GoldbachConjecture { + private GoldbachConjecture() { + } + public record Result(int number1, int number2) { + } + + public static Result getPrimeSum(int number) { + if (number <= 2 || number % 2 != 0) { + throw new IllegalArgumentException("Number must be even and greater than 2."); + } + + for (int i = 0; i <= number / 2; i++) { + if (isPrime(i) && isPrime(number - i)) { + return new Result(i, number - i); + } + } + throw new IllegalStateException("No valid prime sum found."); // Should not occur + } +} diff --git a/src/main/java/com/thealgorithms/maths/KaratsubaMultiplication.java b/src/main/java/com/thealgorithms/maths/KaratsubaMultiplication.java new file mode 100644 index 000000000000..298fcb7e85f8 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/KaratsubaMultiplication.java @@ -0,0 +1,93 @@ +package com.thealgorithms.maths; + +import java.math.BigInteger; + +/** + * This class provides an implementation of the Karatsuba multiplication algorithm. + * + *

+ * Karatsuba multiplication is a divide-and-conquer algorithm for multiplying two large + * numbers. It is faster than the classical multiplication algorithm and reduces the + * time complexity to O(n^1.585) by breaking the multiplication of two n-digit numbers + * into three multiplications of n/2-digit numbers. + *

+ * + *

+ * The main idea of the Karatsuba algorithm is based on the following observation: + *

+ * + *
+ * Let x and y be two numbers:
+ * x = a * 10^m + b
+ * y = c * 10^m + d
+ *
+ * Then, the product of x and y can be expressed as:
+ * x * y = (a * c) * 10^(2*m) + ((a * d) + (b * c)) * 10^m + (b * d)
+ * 
+ * + * The Karatsuba algorithm calculates this more efficiently by reducing the number of + * multiplications from four to three by using the identity: + * + *
+ * (a + b)(c + d) = ac + ad + bc + bd
+ * 
+ * + *

+ * The recursion continues until the numbers are small enough to multiply directly using + * the traditional method. + *

+ */ +public final class KaratsubaMultiplication { + + /** + * Private constructor to hide the implicit public constructor + */ + private KaratsubaMultiplication() { + } + + /** + * Multiplies two large numbers using the Karatsuba algorithm. + * + *

+ * This method recursively splits the numbers into smaller parts until they are + * small enough to be multiplied directly using the traditional method. + *

+ * + * @param x The first large number to be multiplied (BigInteger). + * @param y The second large number to be multiplied (BigInteger). + * @return The product of the two numbers (BigInteger). + */ + public static BigInteger karatsuba(BigInteger x, BigInteger y) { + // Base case: when numbers are small enough, use direct multiplication + // If the number is 4 bits or smaller, switch to the classical method + if (x.bitLength() <= 4 || y.bitLength() <= 4) { + return x.multiply(y); + } + + // Find the maximum bit length of the two numbers + int n = Math.max(x.bitLength(), y.bitLength()); + + // Split the numbers in the middle + int m = n / 2; + + // High and low parts of the first number x (x = a * 10^m + b) + BigInteger high1 = x.shiftRight(m); // a = x / 2^m (higher part) + BigInteger low1 = x.subtract(high1.shiftLeft(m)); // b = x - a * 2^m (lower part) + + // High and low parts of the second number y (y = c * 10^m + d) + BigInteger high2 = y.shiftRight(m); // c = y / 2^m (higher part) + BigInteger low2 = y.subtract(high2.shiftLeft(m)); // d = y - c * 2^m (lower part) + + // Recursively calculate three products + BigInteger z0 = karatsuba(low1, low2); // z0 = b * d (low1 * low2) + BigInteger z1 = karatsuba(low1.add(high1), low2.add(high2)); // z1 = (a + b) * (c + d) + BigInteger z2 = karatsuba(high1, high2); // z2 = a * c (high1 * high2) + + // Combine the results using Karatsuba's formula + // z0 + ((z1 - z2 - z0) << m) + (z2 << 2m) + return z2 + .shiftLeft(2 * m) // z2 * 10^(2*m) + .add(z1.subtract(z2).subtract(z0).shiftLeft(m)) // (z1 - z2 - z0) * 10^m + .add(z0); // z0 + } +} diff --git a/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java b/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java index f5ff50337bc7..03f18aca786f 100644 --- a/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java +++ b/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java @@ -1,31 +1,38 @@ package com.thealgorithms.maths; -/* This is a program to check if a number is a Krishnamurthy number or not. -A number is a Krishnamurthy number if the sum of the factorials of the digits of the number is equal -to the number itself. For example, 1, 2 and 145 are Krishnamurthy numbers. Krishnamurthy number is -also referred to as a Strong number. +/** + * Utility class for checking if a number is a Krishnamurthy number. + * + * A Krishnamurthy number (also known as a Strong number) is a number whose sum of the factorials of its digits is equal to the number itself. + * + * For example, 145 is a Krishnamurthy number because 1! + 4! + 5! = 1 + 24 + 120 = 145. + * Example usage: + *
+ * boolean isKrishnamurthy = KrishnamurthyNumber.isKrishnamurthy(145);
+ * System.out.println(isKrishnamurthy); // Output: true
+ *
+ * isKrishnamurthy = KrishnamurthyNumber.isKrishnamurthy(123);
+ * System.out.println(isKrishnamurthy); // Output: false
+ * 
*/ -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; - public final class KrishnamurthyNumber { + private KrishnamurthyNumber() { } - // returns True if the number is a Krishnamurthy number and False if it is not. - - public static boolean isKMurthy(int n) { - // initialising the variable s that will store the sum of the factorials of the digits to 0 - int s = 0; - // storing the number n in a temporary variable tmp + /** + * Checks if a number is a Krishnamurthy number. + * + * @param n The number to check + * @return true if the number is a Krishnamurthy number, false otherwise + */ + public static boolean isKrishnamurthy(int n) { int tmp = n; + int s = 0; - // Krishnamurthy numbers are positive if (n <= 0) { return false; - } // checking if the number is a Krishnamurthy number - else { + } else { while (n != 0) { // initialising the variable fact that will store the factorials of the digits int fact = 1; @@ -43,15 +50,4 @@ public static boolean isKMurthy(int n) { return tmp == s; } } - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - System.out.println("Enter a number to check if it is a Krishnamurthy number: "); - int n = Integer.parseInt(br.readLine()); - if (isKMurthy(n)) { - System.out.println(n + " is a Krishnamurthy number."); - } else { - System.out.println(n + " is NOT a Krishnamurthy number."); - } - } } diff --git a/src/main/java/com/thealgorithms/maths/MathBuilder.java b/src/main/java/com/thealgorithms/maths/MathBuilder.java new file mode 100644 index 000000000000..1cf3d8b7fc9a --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/MathBuilder.java @@ -0,0 +1,503 @@ +package com.thealgorithms.maths; + +import java.text.DecimalFormat; +import java.util.Random; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Author: Sadiul Hakim : https://github.com/sadiul-hakim + * Profession: Backend Engineer + * Date: Oct 20, 2024 + */ +public final class MathBuilder { + private final double result; + + private MathBuilder(Builder builder) { + this.result = builder.number; + } + + // Returns final result + public double get() { + return result; + } + + // Return result in long + public long toLong() { + try { + if (Double.isNaN(result)) { + throw new IllegalArgumentException("Cannot convert NaN to long!"); + } + if (result == Double.POSITIVE_INFINITY) { + return Long.MAX_VALUE; + } + if (result == Double.NEGATIVE_INFINITY) { + return Long.MIN_VALUE; + } + if (result > Long.MAX_VALUE) { + return Long.MAX_VALUE; + } + if (result < Long.MIN_VALUE) { + return Long.MIN_VALUE; + } + return Math.round(result); + } catch (Exception ex) { + return 0; + } + } + + public static class Builder { + private double number; + private double sideNumber; + private boolean inParenthesis; + private double memory = 0; + + public Builder() { + number = 0; + } + + public Builder(double num) { + number = num; + } + + public Builder add(double num) { + if (inParenthesis) { + sideNumber += num; + } else { + number += num; + } + return this; + } + + // Takes a number and a condition, only does the operation if condition is true. + public Builder addIf(double num, BiFunction condition) { + if (!condition.apply(number, num)) { + return this; + } + if (inParenthesis) { + sideNumber += num; + } else { + number += num; + } + return this; + } + + public Builder minus(double num) { + if (inParenthesis) { + sideNumber -= num; + } else { + number -= num; + } + return this; + } + + // Takes a number and a condition, only does the operation if condition is true. + public Builder minusIf(double num, BiFunction condition) { + if (!condition.apply(number, num)) { + return this; + } + if (inParenthesis) { + sideNumber -= num; + } else { + number -= num; + } + return this; + } + + // Generates a random number and sets to NUMBER + public Builder rand(long seed) { + if (number != 0) { + throw new RuntimeException("Number must be zero for random assignment!"); + } + Random random = new Random(); + number = random.nextDouble(seed); + return this; + } + + // Takes PI value and sets to NUMBER + public Builder pi() { + if (number != 0) { + throw new RuntimeException("Number must be zero for PI assignment!"); + } + number = Math.PI; + return this; + } + + // Takes E value and sets to NUMBER + public Builder e() { + if (number != 0) { + throw new RuntimeException("Number must be zero for E assignment!"); + } + number = Math.E; + return this; + } + + public Builder randomInRange(double min, double max) { + if (number != 0) { + throw new RuntimeException("Number must be zero for random assignment!"); + } + Random random = new Random(); + number = min + (max - min) * random.nextDouble(); + return this; + } + + public Builder toDegrees() { + if (inParenthesis) { + sideNumber = Math.toDegrees(sideNumber); + } else { + number = Math.toDegrees(number); + } + return this; + } + + public Builder max(double num) { + if (inParenthesis) { + sideNumber = Math.max(sideNumber, num); + } else { + number = Math.max(number, num); + } + return this; + } + + public Builder min(double num) { + if (inParenthesis) { + sideNumber = Math.min(sideNumber, num); + } else { + number = Math.min(number, num); + } + return this; + } + + public Builder multiply(double num) { + if (inParenthesis) { + sideNumber *= num; + } else { + number *= num; + } + return this; + } + + // Takes a number and a condition, only does the operation if condition is true. + public Builder multiplyIf(double num, BiFunction condition) { + if (!condition.apply(number, num)) { + return this; + } + if (inParenthesis) { + sideNumber *= num; + } else { + number *= num; + } + return this; + } + + public Builder divide(double num) { + if (num == 0) { + return this; + } + if (inParenthesis) { + sideNumber /= num; + } else { + number /= num; + } + return this; + } + + // Takes a number and a condition, only does the operation if condition is true. + public Builder divideIf(double num, BiFunction condition) { + if (num == 0) { + return this; + } + if (!condition.apply(number, num)) { + return this; + } + if (inParenthesis) { + sideNumber /= num; + } else { + number /= num; + } + return this; + } + + public Builder mod(double num) { + if (inParenthesis) { + sideNumber %= num; + } else { + number %= num; + } + return this; + } + + // Takes a number and a condition, only does the operation if condition is true. + public Builder modIf(double num, BiFunction condition) { + if (!condition.apply(number, num)) { + return this; + } + if (inParenthesis) { + sideNumber %= num; + } else { + number %= num; + } + return this; + } + + public Builder pow(double num) { + if (inParenthesis) { + sideNumber = Math.pow(sideNumber, num); + } else { + number = Math.pow(number, num); + } + return this; + } + + public Builder sqrt() { + if (inParenthesis) { + sideNumber = Math.sqrt(sideNumber); + } else { + number = Math.sqrt(number); + } + return this; + } + + public Builder round() { + if (inParenthesis) { + sideNumber = Math.round(sideNumber); + } else { + number = Math.round(number); + } + return this; + } + + public Builder floor() { + if (inParenthesis) { + sideNumber = Math.floor(sideNumber); + } else { + number = Math.floor(number); + } + return this; + } + + public Builder ceil() { + if (inParenthesis) { + sideNumber = Math.ceil(sideNumber); + } else { + number = Math.ceil(number); + } + return this; + } + + public Builder abs() { + if (inParenthesis) { + sideNumber = Math.abs(sideNumber); + } else { + number = Math.abs(number); + } + return this; + } + + public Builder cbrt() { + if (inParenthesis) { + sideNumber = Math.cbrt(sideNumber); + } else { + number = Math.cbrt(number); + } + return this; + } + + public Builder log() { + if (inParenthesis) { + sideNumber = Math.log(sideNumber); + } else { + number = Math.log(number); + } + return this; + } + + public Builder log10() { + if (inParenthesis) { + sideNumber = Math.log10(sideNumber); + } else { + number = Math.log10(number); + } + return this; + } + + public Builder sin() { + if (inParenthesis) { + sideNumber = Math.sin(sideNumber); + } else { + number = Math.sin(number); + } + return this; + } + + public Builder cos() { + if (inParenthesis) { + sideNumber = Math.cos(sideNumber); + } else { + number = Math.cos(number); + } + return this; + } + + public Builder tan() { + if (inParenthesis) { + sideNumber = Math.tan(sideNumber); + } else { + number = Math.tan(number); + } + return this; + } + + public Builder sinh() { + if (inParenthesis) { + sideNumber = Math.sinh(sideNumber); + } else { + number = Math.sinh(number); + } + return this; + } + + public Builder cosh() { + if (inParenthesis) { + sideNumber = Math.cosh(sideNumber); + } else { + number = Math.cosh(number); + } + return this; + } + + public Builder tanh() { + if (inParenthesis) { + sideNumber = Math.tanh(sideNumber); + } else { + number = Math.tanh(number); + } + return this; + } + + public Builder exp() { + if (inParenthesis) { + sideNumber = Math.exp(sideNumber); + } else { + number = Math.exp(number); + } + return this; + } + + public Builder toRadians() { + if (inParenthesis) { + sideNumber = Math.toRadians(sideNumber); + } else { + number = Math.toRadians(number); + } + return this; + } + + // Remembers the NUMBER + public Builder remember() { + memory = number; + return this; + } + + // Recalls the NUMBER + public Builder recall(boolean cleanMemory) { + number = memory; + if (cleanMemory) { + memory = 0; + } + return this; + } + + // Recalls the NUMBER on condition + public Builder recallIf(Function condition, boolean cleanMemory) { + if (!condition.apply(number)) { + return this; + } + number = memory; + if (cleanMemory) { + memory = 0; + } + return this; + } + + // Replaces NUMBER with given number + public Builder set(double num) { + if (number != 0) { + throw new RuntimeException("Number must be zero to set!"); + } + number = num; + return this; + } + + // Replaces NUMBER with given number on condition + public Builder setIf(double num, BiFunction condition) { + if (number != 0) { + throw new RuntimeException("Number must be zero to set!"); + } + if (condition.apply(number, num)) { + number = num; + } + return this; + } + + // Prints current NUMBER + public Builder print() { + System.out.println("MathBuilder Result :: " + number); + return this; + } + + public Builder openParenthesis(double num) { + sideNumber = num; + inParenthesis = true; + return this; + } + + public Builder closeParenthesisAndPlus() { + number += sideNumber; + inParenthesis = false; + sideNumber = 0; + return this; + } + + public Builder closeParenthesisAndMinus() { + number -= sideNumber; + inParenthesis = false; + sideNumber = 0; + return this; + } + + public Builder closeParenthesisAndMultiply() { + number *= sideNumber; + inParenthesis = false; + sideNumber = 0; + return this; + } + + public Builder closeParenthesisAndDivide() { + number /= sideNumber; + inParenthesis = false; + sideNumber = 0; + return this; + } + + public Builder format(String format) { + DecimalFormat formater = new DecimalFormat(format); + String num = formater.format(number); + number = Double.parseDouble(num); + return this; + } + + public Builder format(int decimalPlace) { + String pattern = "." + + "#".repeat(decimalPlace); + DecimalFormat formater = new DecimalFormat(pattern); + String num = formater.format(number); + number = Double.parseDouble(num); + return this; + } + + public MathBuilder build() { + return new MathBuilder(this); + } + } +} diff --git a/src/main/java/com/thealgorithms/maths/NthUglyNumber.java b/src/main/java/com/thealgorithms/maths/NthUglyNumber.java index 90507701332f..2da22c4c8696 100644 --- a/src/main/java/com/thealgorithms/maths/NthUglyNumber.java +++ b/src/main/java/com/thealgorithms/maths/NthUglyNumber.java @@ -1,7 +1,9 @@ package com.thealgorithms.maths; +import static java.util.Collections.singletonList; + import java.util.ArrayList; -import java.util.Arrays; +import java.util.Map; import org.apache.commons.lang3.tuple.MutablePair; /** @@ -15,7 +17,7 @@ * - the base [2, 3, 5] ugly numbers are the same as base [5, 6, 2, 3, 5] ugly numbers */ public class NthUglyNumber { - private ArrayList uglyNumbers = new ArrayList<>(Arrays.asList(1L)); + private ArrayList uglyNumbers = new ArrayList<>(singletonList(1L)); private ArrayList> positions = new ArrayList<>(); /** @@ -64,7 +66,7 @@ private void updatePositions() { } } - private long computeCandidate(final MutablePair entry) { + private long computeCandidate(final Map.Entry entry) { return entry.getKey() * uglyNumbers.get(entry.getValue()); } diff --git a/src/main/java/com/thealgorithms/maths/PerfectNumber.java b/src/main/java/com/thealgorithms/maths/PerfectNumber.java index 2a935b067094..f299d08e5d27 100644 --- a/src/main/java/com/thealgorithms/maths/PerfectNumber.java +++ b/src/main/java/com/thealgorithms/maths/PerfectNumber.java @@ -63,7 +63,7 @@ public static boolean isPerfectNumber2(int n) { // if n is a perfect square then its root was added twice in above loop, so subtracting root // from sum if (root == (int) root) { - sum -= root; + sum -= (int) root; } return sum == n; diff --git a/src/main/java/com/thealgorithms/maths/PerfectSquare.java b/src/main/java/com/thealgorithms/maths/PerfectSquare.java index fbc7a6f19bd0..e9318bd7d805 100644 --- a/src/main/java/com/thealgorithms/maths/PerfectSquare.java +++ b/src/main/java/com/thealgorithms/maths/PerfectSquare.java @@ -18,4 +18,16 @@ public static boolean isPerfectSquare(final int number) { final int sqrt = (int) Math.sqrt(number); return sqrt * sqrt == number; } + + /** + * Check if a number is perfect square or not + * + * @param number number to be checked + * @return {@code true} if {@code number} is perfect square, otherwise + * {@code false} + */ + public static boolean isPerfectSquareUsingPow(long number) { + long a = (long) Math.pow(number, 1.0 / 2); + return a * a == number; + } } diff --git a/src/main/java/com/thealgorithms/maths/LiouvilleLambdaFunction.java b/src/main/java/com/thealgorithms/maths/Prime/LiouvilleLambdaFunction.java similarity index 93% rename from src/main/java/com/thealgorithms/maths/LiouvilleLambdaFunction.java rename to src/main/java/com/thealgorithms/maths/Prime/LiouvilleLambdaFunction.java index c0f55f5e3485..73535b3aedae 100644 --- a/src/main/java/com/thealgorithms/maths/LiouvilleLambdaFunction.java +++ b/src/main/java/com/thealgorithms/maths/Prime/LiouvilleLambdaFunction.java @@ -1,4 +1,4 @@ -package com.thealgorithms.maths; +package com.thealgorithms.maths.Prime; /* * Java program for liouville lambda function @@ -24,7 +24,7 @@ private LiouvilleLambdaFunction() { * -1 when number has odd number of prime factors * @throws IllegalArgumentException when number is negative */ - static int liouvilleLambda(int number) { + public static int liouvilleLambda(int number) { if (number <= 0) { // throw exception when number is less than or is zero throw new IllegalArgumentException("Number must be greater than zero."); diff --git a/src/main/java/com/thealgorithms/maths/MillerRabinPrimalityCheck.java b/src/main/java/com/thealgorithms/maths/Prime/MillerRabinPrimalityCheck.java similarity index 98% rename from src/main/java/com/thealgorithms/maths/MillerRabinPrimalityCheck.java rename to src/main/java/com/thealgorithms/maths/Prime/MillerRabinPrimalityCheck.java index f889213abfcb..debe3a214a32 100644 --- a/src/main/java/com/thealgorithms/maths/MillerRabinPrimalityCheck.java +++ b/src/main/java/com/thealgorithms/maths/Prime/MillerRabinPrimalityCheck.java @@ -1,4 +1,4 @@ -package com.thealgorithms.maths; +package com.thealgorithms.maths.Prime; import java.util.Random; diff --git a/src/main/java/com/thealgorithms/maths/MobiusFunction.java b/src/main/java/com/thealgorithms/maths/Prime/MobiusFunction.java similarity index 96% rename from src/main/java/com/thealgorithms/maths/MobiusFunction.java rename to src/main/java/com/thealgorithms/maths/Prime/MobiusFunction.java index 915d0d9a6dae..3d4e4eff0f03 100644 --- a/src/main/java/com/thealgorithms/maths/MobiusFunction.java +++ b/src/main/java/com/thealgorithms/maths/Prime/MobiusFunction.java @@ -1,4 +1,4 @@ -package com.thealgorithms.maths; +package com.thealgorithms.maths.Prime; /* * Java program for mobius function @@ -25,7 +25,7 @@ private MobiusFunction() { * 0 when number has repeated prime factor * -1 when number has odd number of prime factors */ - static int mobius(int number) { + public static int mobius(int number) { if (number <= 0) { // throw exception when number is less than or is zero throw new IllegalArgumentException("Number must be greater than zero."); diff --git a/src/main/java/com/thealgorithms/maths/PrimeCheck.java b/src/main/java/com/thealgorithms/maths/Prime/PrimeCheck.java similarity index 98% rename from src/main/java/com/thealgorithms/maths/PrimeCheck.java rename to src/main/java/com/thealgorithms/maths/Prime/PrimeCheck.java index 628a819aeba4..91c490f70aef 100644 --- a/src/main/java/com/thealgorithms/maths/PrimeCheck.java +++ b/src/main/java/com/thealgorithms/maths/Prime/PrimeCheck.java @@ -1,4 +1,4 @@ -package com.thealgorithms.maths; +package com.thealgorithms.maths.Prime; import java.util.Scanner; diff --git a/src/main/java/com/thealgorithms/maths/PrimeFactorization.java b/src/main/java/com/thealgorithms/maths/Prime/PrimeFactorization.java similarity index 95% rename from src/main/java/com/thealgorithms/maths/PrimeFactorization.java rename to src/main/java/com/thealgorithms/maths/Prime/PrimeFactorization.java index 9ac50fd9043b..e12002b3d8c7 100644 --- a/src/main/java/com/thealgorithms/maths/PrimeFactorization.java +++ b/src/main/java/com/thealgorithms/maths/Prime/PrimeFactorization.java @@ -1,4 +1,4 @@ -package com.thealgorithms.maths; +package com.thealgorithms.maths.Prime; /* * Authors: diff --git a/src/main/java/com/thealgorithms/maths/SquareFreeInteger.java b/src/main/java/com/thealgorithms/maths/Prime/SquareFreeInteger.java similarity index 97% rename from src/main/java/com/thealgorithms/maths/SquareFreeInteger.java rename to src/main/java/com/thealgorithms/maths/Prime/SquareFreeInteger.java index 22e9fee00605..15c0a8a691cd 100644 --- a/src/main/java/com/thealgorithms/maths/SquareFreeInteger.java +++ b/src/main/java/com/thealgorithms/maths/Prime/SquareFreeInteger.java @@ -1,4 +1,4 @@ -package com.thealgorithms.maths; +package com.thealgorithms.maths.Prime; /* * Java program for Square free integer * This class has a function which checks diff --git a/src/main/java/com/thealgorithms/maths/PronicNumber.java b/src/main/java/com/thealgorithms/maths/PronicNumber.java index 4891cf3c63b3..c2ad2a8139c8 100644 --- a/src/main/java/com/thealgorithms/maths/PronicNumber.java +++ b/src/main/java/com/thealgorithms/maths/PronicNumber.java @@ -21,6 +21,9 @@ private PronicNumber() { * @return true if input number is a pronic number, false otherwise */ static boolean isPronic(int inputNumber) { + if (inputNumber == 0) { + return true; + } // Iterating from 0 to input_number for (int i = 0; i <= inputNumber; i++) { // Checking if product of i and (i+1) is equals input_number @@ -34,4 +37,15 @@ static boolean isPronic(int inputNumber) { // equals input_number return false; } + + /** + * This method checks if the given number is pronic number or non-pronic number using square root of number for finding divisors + * + * @param number Integer value which is to be checked if is a pronic number or not + * @return true if input number is a pronic number, false otherwise + */ + public static boolean isPronicNumber(int number) { + int squareRoot = (int) Math.sqrt(number); // finding just smaller divisor of the number than its square root. + return squareRoot * (squareRoot + 1) == number; + } } diff --git a/src/main/java/com/thealgorithms/maths/QuadraticEquationSolver.java b/src/main/java/com/thealgorithms/maths/QuadraticEquationSolver.java new file mode 100644 index 000000000000..cd654c5dc023 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/QuadraticEquationSolver.java @@ -0,0 +1,60 @@ +package com.thealgorithms.maths; + +/** + * This class represents a complex number which has real and imaginary part + */ +class ComplexNumber { + Double real; + Double imaginary; + + ComplexNumber(double real, double imaginary) { + this.real = real; + this.imaginary = imaginary; + } + + ComplexNumber(double real) { + this.real = real; + this.imaginary = null; + } +} + +/** + * Quadratic Equation Formula is used to find + * the roots of a quadratic equation of the form ax^2 + bx + c = 0 + * + * @see Quadratic Equation + */ +public class QuadraticEquationSolver { + /** + * Function takes in the coefficients of the quadratic equation + * + * @param a is the coefficient of x^2 + * @param b is the coefficient of x + * @param c is the constant + * @return roots of the equation which are ComplexNumber type + */ + public ComplexNumber[] solveEquation(double a, double b, double c) { + double discriminant = b * b - 4 * a * c; + + // if discriminant is positive, roots will be different + if (discriminant > 0) { + return new ComplexNumber[] {new ComplexNumber((-b + Math.sqrt(discriminant)) / (2 * a)), new ComplexNumber((-b - Math.sqrt(discriminant)) / (2 * a))}; + } + + // if discriminant is zero, roots will be same + if (discriminant == 0) { + return new ComplexNumber[] {new ComplexNumber((-b) / (2 * a))}; + } + + // if discriminant is negative, roots will have imaginary parts + if (discriminant < 0) { + double realPart = -b / (2 * a); + double imaginaryPart = Math.sqrt(-discriminant) / (2 * a); + + return new ComplexNumber[] {new ComplexNumber(realPart, imaginaryPart), new ComplexNumber(realPart, -imaginaryPart)}; + } + + // return no roots + return new ComplexNumber[] {}; + } +} diff --git a/src/main/java/com/thealgorithms/maths/SolovayStrassenPrimalityTest.java b/src/main/java/com/thealgorithms/maths/SolovayStrassenPrimalityTest.java new file mode 100644 index 000000000000..caa1abfc3203 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/SolovayStrassenPrimalityTest.java @@ -0,0 +1,133 @@ +package com.thealgorithms.maths; + +import java.util.Random; + +/** + * This class implements the Solovay-Strassen primality test, + * which is a probabilistic algorithm to determine whether a number is prime. + * The algorithm is based on properties of the Jacobi symbol and modular exponentiation. + * + * For more information, go to {@link https://en.wikipedia.org/wiki/Solovay%E2%80%93Strassen_primality_test} + */ +final class SolovayStrassenPrimalityTest { + + private final Random random; + + /** + * Constructs a SolovayStrassenPrimalityTest instance with a specified seed for randomness. + * + * @param seed the seed for generating random numbers + */ + private SolovayStrassenPrimalityTest(int seed) { + random = new Random(seed); + } + + /** + * Factory method to create an instance of SolovayStrassenPrimalityTest. + * + * @param seed the seed for generating random numbers + * @return a new instance of SolovayStrassenPrimalityTest + */ + public static SolovayStrassenPrimalityTest getSolovayStrassenPrimalityTest(int seed) { + return new SolovayStrassenPrimalityTest(seed); + } + + /** + * Calculates modular exponentiation using the method of exponentiation by squaring. + * + * @param base the base number + * @param exponent the exponent + * @param mod the modulus + * @return (base^exponent) mod mod + */ + private static long calculateModularExponentiation(long base, long exponent, long mod) { + long x = 1; // This will hold the result of (base^exponent) % mod + long y = base; // This holds the current base value being squared + + while (exponent > 0) { + // If exponent is odd, multiply the current base (y) with x + if (exponent % 2 == 1) { + x = x * y % mod; // Update result with current base + } + // Square the base for the next iteration + y = y * y % mod; // Update base to be y^2 + exponent = exponent / 2; // Halve the exponent for next iteration + } + + return x % mod; // Return final result after all iterations + } + + /** + * Computes the Jacobi symbol (a/n), which is a generalization of the Legendre symbol. + * + * @param a the numerator + * @param num the denominator (must be an odd positive integer) + * @return the Jacobi symbol value: 1, -1, or 0 + */ + public int calculateJacobi(long a, long num) { + // Check if num is non-positive or even; Jacobi symbol is not defined in these cases + if (num <= 0 || num % 2 == 0) { + return 0; + } + + a = a % num; // Reduce a modulo num to simplify calculations + int jacobi = 1; // Initialize Jacobi symbol value + + while (a != 0) { + // While a is even, reduce it and adjust jacobi based on properties of num + while (a % 2 == 0) { + a /= 2; // Divide a by 2 until it becomes odd + long nMod8 = num % 8; // Get num modulo 8 to check conditions for jacobi adjustment + if (nMod8 == 3 || nMod8 == 5) { + jacobi = -jacobi; // Flip jacobi sign based on properties of num modulo 8 + } + } + + long temp = a; // Temporarily store value of a + a = num; // Set a to be num for next iteration + num = temp; // Set num to be previous value of a + + // Adjust jacobi based on properties of both numbers when both are odd and congruent to 3 modulo 4 + if (a % 4 == 3 && num % 4 == 3) { + jacobi = -jacobi; // Flip jacobi sign again based on congruences + } + + a = a % num; // Reduce a modulo num for next iteration of Jacobi computation + } + + return (num == 1) ? jacobi : 0; // If num reduces to 1, return jacobi value, otherwise return 0 (not defined) + } + + /** + * Performs the Solovay-Strassen primality test on a given number. + * + * @param num the number to be tested for primality + * @param iterations the number of iterations to run for accuracy + * @return true if num is likely prime, false if it is composite + */ + public boolean solovayStrassen(long num, int iterations) { + if (num <= 1) { + return false; // Numbers <=1 are not prime by definition. + } + if (num <= 3) { + return true; // Numbers <=3 are prime. + } + + for (int i = 0; i < iterations; i++) { + long r = Math.abs(random.nextLong() % (num - 1)) + 2; // Generate a non-negative random number. + long a = r % (num - 1) + 1; // Choose random 'a' in range [1, n-1]. + + long jacobi = (num + calculateJacobi(a, num)) % num; + // Calculate Jacobi symbol and adjust it modulo n. + + long mod = calculateModularExponentiation(a, (num - 1) / 2, num); + // Calculate modular exponentiation: a^((n-1)/2) mod n. + + if (jacobi == 0 || mod != jacobi) { + return false; // If Jacobi symbol is zero or doesn't match modular result, n is composite. + } + } + + return true; // If no contradictions found after all iterations, n is likely prime. + } +} diff --git a/src/main/java/com/thealgorithms/maths/SumOfOddNumbers.java b/src/main/java/com/thealgorithms/maths/SumOfOddNumbers.java new file mode 100644 index 000000000000..c0a1e782659a --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/SumOfOddNumbers.java @@ -0,0 +1,25 @@ +package com.thealgorithms.maths; + +/** + * This program calculates the sum of the first n odd numbers. + * + * https://www.cuemath.com/algebra/sum-of-odd-numbers/ + */ + +public final class SumOfOddNumbers { + private SumOfOddNumbers() { + } + + /** + * Calculate sum of the first n odd numbers + * + * @param n the number of odd numbers to sum + * @return sum of the first n odd numbers + */ + public static int sumOfFirstNOddNumbers(final int n) { + if (n < 0) { + throw new IllegalArgumentException("n must be non-negative."); + } + return n * n; + } +} diff --git a/src/main/java/com/thealgorithms/maths/TwinPrime.java b/src/main/java/com/thealgorithms/maths/TwinPrime.java index ef8de0d1018e..f4e546a2d7a4 100644 --- a/src/main/java/com/thealgorithms/maths/TwinPrime.java +++ b/src/main/java/com/thealgorithms/maths/TwinPrime.java @@ -9,6 +9,8 @@ * * */ +import com.thealgorithms.maths.Prime.PrimeCheck; + public final class TwinPrime { private TwinPrime() { } diff --git a/src/main/java/com/thealgorithms/maths/UniformNumbers.java b/src/main/java/com/thealgorithms/maths/UniformNumbers.java new file mode 100644 index 000000000000..c83783aab0b3 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/UniformNumbers.java @@ -0,0 +1,50 @@ +package com.thealgorithms.maths; + +/** + * A positive integer is considered uniform if all + * of its digits are equal. For example, 222 is uniform, + * while 223 is not. + * Given two positive integers a and b, determine the + * number of uniform integers between a and b. + */ +public final class UniformNumbers { + // Private constructor to prevent instantiation of the utility class + private UniformNumbers() { + // Prevent instantiation + } + /** + * This function will find the number of uniform numbers + * from 1 to num + * @param num upper limit to find the uniform numbers + * @return the count of uniform numbers between 1 and num + */ + public static int uniformNumbers(int num) { + String numStr = Integer.toString(num); + int uniformCount = (numStr.length() - 1) * 9; + int finalUniform = Integer.parseInt(String.valueOf(numStr.charAt(0)).repeat(numStr.length())); + + if (finalUniform <= num) { + uniformCount += Integer.parseInt(String.valueOf(numStr.charAt(0))); + } else { + uniformCount += Integer.parseInt(String.valueOf(numStr.charAt(0))) - 1; + } + + return uniformCount; + } + /** + * This function will calculate the number of uniform numbers + * between a and b + * @param a lower bound of range + * @param b upper bound of range + * @return the count of uniform numbers between a and b + */ + public static int countUniformIntegers(int a, int b) { + if (b > a && b > 0 && a > 0) { + return uniformNumbers(b) - uniformNumbers(a - 1); + } else if (b == a) { + return 1; + } else { + return 0; + } + } +} diff --git a/src/main/java/com/thealgorithms/maths/VampireNumber.java b/src/main/java/com/thealgorithms/maths/VampireNumber.java index d64c82c6e68e..45bb9a587778 100644 --- a/src/main/java/com/thealgorithms/maths/VampireNumber.java +++ b/src/main/java/com/thealgorithms/maths/VampireNumber.java @@ -1,79 +1,48 @@ package com.thealgorithms.maths; import java.util.ArrayList; -import java.util.Collections; /** - * n number theory, a vampire number (or true vampire number) is a composite + * In number theory, a vampire number (or true vampire number) is a composite * natural number with an even number of digits, that can be factored into two * natural numbers each with half as many digits as the original number and not * both with trailing zeroes, where the two factors contain precisely all the * digits of the original number, in any order, counting multiplicity. The first - * vampire number is 1260 = 21 × 60. * + * vampire number is 1260 = 21 × 60. * - *

- * link: https://en.wikipedia.org/wiki/Vampire_number * - * - *

+ * @see Vampire number on Wikipedia */ public final class VampireNumber { + // Forbid instantiation. private VampireNumber() { } - public static void main(String[] args) { - test(10, 1000); - } - - static void test(int startValue, int stopValue) { - int countofRes = 1; - StringBuilder res = new StringBuilder(); - - for (int i = startValue; i <= stopValue; i++) { - for (int j = i; j <= stopValue; j++) { - // System.out.println(i+ " "+ j); - if (isVampireNumber(i, j, true)) { - countofRes++; - res.append("" + countofRes + ": = ( " + i + "," + j + " = " + i * j + ")" - + "\n"); - } - } - } - System.out.println(res); - } - - static boolean isVampireNumber(int a, int b, boolean noPseudoVamireNumbers) { - // this is for pseudoVampireNumbers pseudovampire number need not be of length n/2 digits - // for example 126 = 6 x 21 - if (noPseudoVamireNumbers) { - if (a * 10 <= b || b * 10 <= a) { - return false; - } + static boolean isVampireNumber(int a, int b, boolean ignorePseudoVampireNumbers) { + // Pseudo vampire numbers don't have to be of n/2 digits. E.g., 126 = 6 x 21 is such a number. + if (ignorePseudoVampireNumbers && String.valueOf(a).length() != String.valueOf(b).length()) { + return false; } - String mulDigits = splitIntoDigits(a * b, 0); - String faktorDigits = splitIntoDigits(a, b); + String mulDigits = splitIntoSortedDigits(a * b); + String factorDigits = splitIntoSortedDigits(a, b); - return mulDigits.equals(faktorDigits); + return mulDigits.equals(factorDigits); } - // methode to Split the numbers to Digits - static String splitIntoDigits(int num, int num2) { - StringBuilder res = new StringBuilder(); - + // Method to split a pair of numbers to digits and sort them in the ascending order. + static String splitIntoSortedDigits(int... nums) { + // Collect all digits in a list. ArrayList digits = new ArrayList<>(); - while (num > 0) { - digits.add(num % 10); - num /= 10; - } - while (num2 > 0) { - digits.add(num2 % 10); - num2 /= 10; - } - Collections.sort(digits); - for (int i : digits) { - res.append(i); + for (int num : nums) { + while (num > 0) { + digits.add(num % 10); + num /= 10; + } } + // Sort all digits and convert to String. + StringBuilder res = new StringBuilder(); + digits.stream().sorted().forEach(res::append); return res.toString(); } } diff --git a/src/main/java/com/thealgorithms/misc/InverseOfMatrix.java b/src/main/java/com/thealgorithms/matrix/InverseOfMatrix.java similarity index 98% rename from src/main/java/com/thealgorithms/misc/InverseOfMatrix.java rename to src/main/java/com/thealgorithms/matrix/InverseOfMatrix.java index 706feab0c69d..13e795a91297 100644 --- a/src/main/java/com/thealgorithms/misc/InverseOfMatrix.java +++ b/src/main/java/com/thealgorithms/matrix/InverseOfMatrix.java @@ -1,4 +1,4 @@ -package com.thealgorithms.misc; +package com.thealgorithms.matrix; /** * This class provides methods to compute the inverse of a square matrix diff --git a/src/main/java/com/thealgorithms/maths/MatrixRank.java b/src/main/java/com/thealgorithms/matrix/MatrixRank.java similarity index 77% rename from src/main/java/com/thealgorithms/maths/MatrixRank.java rename to src/main/java/com/thealgorithms/matrix/MatrixRank.java index 7a628b92dccb..6692b6c37c60 100644 --- a/src/main/java/com/thealgorithms/maths/MatrixRank.java +++ b/src/main/java/com/thealgorithms/matrix/MatrixRank.java @@ -1,4 +1,6 @@ -package com.thealgorithms.maths; +package com.thealgorithms.matrix; + +import static com.thealgorithms.matrix.utils.MatrixUtil.validateInputMatrix; /** * This class provides a method to compute the rank of a matrix. @@ -63,47 +65,6 @@ private static double[][] deepCopy(double[][] matrix) { return matrixCopy; } - 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; - } - - /** - * @brief Checks if the input matrix is a jagged matrix. - * Jagged matrix is a matrix where the number of columns in each row is not the same. - * - * @param matrix The input matrix - * @return True if the input matrix is a jagged matrix, false otherwise - */ - private static boolean isJaggedMatrix(double[][] matrix) { - int numColumns = matrix[0].length; - for (double[] row : matrix) { - if (row.length != numColumns) { - return true; - } - } - return false; - } - /** * @brief The pivot row is the row in the matrix that is used to eliminate other rows and reduce the matrix to its row echelon form. * The pivot row is selected as the first row (from top to bottom) where the value in the current column (the pivot column) is not zero. diff --git a/src/main/java/com/thealgorithms/matrix/MatrixTranspose.java b/src/main/java/com/thealgorithms/matrix/MatrixTranspose.java new file mode 100644 index 000000000000..f91ebc10b8a9 --- /dev/null +++ b/src/main/java/com/thealgorithms/matrix/MatrixTranspose.java @@ -0,0 +1,46 @@ +package com.thealgorithms.matrix; + +/** + * + * + *

Find the Transpose of Matrix!

+ * + * Simply take input from the user and print the matrix before the transpose and + * after the transpose. + * + *

+ * Note: Giving proper comments in your program makes it more user + * friendly and it is assumed as a high quality code. + * + * @author Rajat-Jain29 + * @version 11.0.9 + * @since 2014-03-31 + */ +public final class MatrixTranspose { + private MatrixTranspose() { + } + + /** + * Calculate the transpose of the given matrix. + * + * @param matrix The matrix to be transposed + * @throws IllegalArgumentException if the matrix is empty + * @throws NullPointerException if the matrix is null + * @return The transposed matrix + */ + public static int[][] transpose(int[][] matrix) { + if (matrix == null || matrix.length == 0) { + throw new IllegalArgumentException("Matrix is empty"); + } + + int rows = matrix.length; + int cols = matrix[0].length; + int[][] transposedMatrix = new int[cols][rows]; + for (int i = 0; i < cols; i++) { + for (int j = 0; j < rows; j++) { + transposedMatrix[i][j] = matrix[j][i]; + } + } + return transposedMatrix; + } +} diff --git a/src/main/java/com/thealgorithms/misc/MedianOfMatrix.java b/src/main/java/com/thealgorithms/matrix/MedianOfMatrix.java similarity index 88% rename from src/main/java/com/thealgorithms/misc/MedianOfMatrix.java rename to src/main/java/com/thealgorithms/matrix/MedianOfMatrix.java index d4ddffe8ddd7..c710c60a2d2a 100644 --- a/src/main/java/com/thealgorithms/misc/MedianOfMatrix.java +++ b/src/main/java/com/thealgorithms/matrix/MedianOfMatrix.java @@ -1,4 +1,4 @@ -package com.thealgorithms.misc; +package com.thealgorithms.matrix; import java.util.ArrayList; import java.util.Collections; @@ -13,7 +13,7 @@ public final class MedianOfMatrix { private MedianOfMatrix() { } - public static int median(List> matrix) { + public static int median(Iterable> matrix) { // Flatten the matrix into a 1D list List linear = new ArrayList<>(); for (List row : matrix) { diff --git a/src/main/java/com/thealgorithms/matrix/MirrorOfMatrix.java b/src/main/java/com/thealgorithms/matrix/MirrorOfMatrix.java new file mode 100644 index 000000000000..3a3055f38732 --- /dev/null +++ b/src/main/java/com/thealgorithms/matrix/MirrorOfMatrix.java @@ -0,0 +1,36 @@ +package com.thealgorithms.matrix; + +// Problem Statement + +import com.thealgorithms.matrix.utils.MatrixUtil; + +/* +We have given an array of m x n (where m is the number of rows and n is the number of columns). +Print the new matrix in such a way that the new matrix is the mirror image of the original matrix. + +The Original matrix is: | The Mirror matrix is: +1 2 3 | 3 2 1 +4 5 6 | 6 5 4 +7 8 9 | 9 8 7 + +@author - Aman (https://github.com/Aman28801) +*/ + +public final class MirrorOfMatrix { + private MirrorOfMatrix() { + } + + public static double[][] mirrorMatrix(final double[][] originalMatrix) { + MatrixUtil.validateInputMatrix(originalMatrix); + + int numRows = originalMatrix.length; + int numCols = originalMatrix[0].length; + + double[][] mirroredMatrix = new double[numRows][numCols]; + + for (int i = 0; i < numRows; i++) { + mirroredMatrix[i] = MatrixUtil.reverseRow(originalMatrix[i]); + } + return mirroredMatrix; + } +} diff --git a/src/main/java/com/thealgorithms/matrix/PrintAMatrixInSpiralOrder.java b/src/main/java/com/thealgorithms/matrix/PrintAMatrixInSpiralOrder.java new file mode 100644 index 000000000000..2757da1f9023 --- /dev/null +++ b/src/main/java/com/thealgorithms/matrix/PrintAMatrixInSpiralOrder.java @@ -0,0 +1,53 @@ +package com.thealgorithms.matrix; + +import java.util.ArrayList; +import java.util.List; + +public class PrintAMatrixInSpiralOrder { + /** + * Search a key in row and column wise sorted matrix + * + * @param matrix matrix to be searched + * @param row number of rows matrix has + * @param col number of columns matrix has + * @author Sadiul Hakim : https://github.com/sadiul-hakim + */ + public List print(int[][] matrix, int row, int col) { + + // r traverses matrix row wise from first + int r = 0; + // c traverses matrix column wise from first + int c = 0; + int i; + List result = new ArrayList<>(); + while (r < row && c < col) { + // print first row of matrix + for (i = c; i < col; i++) { + result.add(matrix[r][i]); + } + // increase r by one because first row printed + r++; + // print last column + for (i = r; i < row; i++) { + result.add(matrix[i][col - 1]); + } + // decrease col by one because last column has been printed + col--; + // print rows from last except printed elements + if (r < row) { + for (i = col - 1; i >= c; i--) { + result.add(matrix[row - 1][i]); + } + row--; + } + // print columns from first except printed elements + if (c < col) { + for (i = row - 1; i >= r; i--) { + result.add(matrix[i][c]); + } + c++; + } + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/others/RotateMatrixBy90Degrees.java b/src/main/java/com/thealgorithms/matrix/RotateMatrixBy90Degrees.java similarity index 98% rename from src/main/java/com/thealgorithms/others/RotateMatrixBy90Degrees.java rename to src/main/java/com/thealgorithms/matrix/RotateMatrixBy90Degrees.java index 6ad0ef024342..9a7f255282ac 100644 --- a/src/main/java/com/thealgorithms/others/RotateMatrixBy90Degrees.java +++ b/src/main/java/com/thealgorithms/matrix/RotateMatrixBy90Degrees.java @@ -1,4 +1,4 @@ -package com.thealgorithms.others; +package com.thealgorithms.matrix; import java.util.Scanner; /** diff --git a/src/main/java/com/thealgorithms/matrix/SolveSystem.java b/src/main/java/com/thealgorithms/matrix/SolveSystem.java new file mode 100644 index 000000000000..9e683bc4dc5c --- /dev/null +++ b/src/main/java/com/thealgorithms/matrix/SolveSystem.java @@ -0,0 +1,71 @@ +package com.thealgorithms.matrix; + +/** + * This class implements an algorithm for solving a system of equations of the form Ax=b using gaussian elimination and back substitution. + * + * @link Gaussian Elimination Wiki + * @see InverseOfMatrix finds the full of inverse of a matrice, but is not required to solve a system. + */ +public final class SolveSystem { + private SolveSystem() { + } + + /** + * Problem: Given a matrix A and vector b, solve the linear system Ax = b for the vector x.\ + *

+ * This OVERWRITES the input matrix to save on memory + * + * @param matrix - a square matrix of doubles + * @param constants - an array of constant + * @return solutions + */ + public static double[] solveSystem(double[][] matrix, double[] constants) { + final double tol = 0.00000001; // tolerance for round off + for (int k = 0; k < matrix.length - 1; k++) { + // find the largest value in column (to avoid zero pivots) + double maxVal = Math.abs(matrix[k][k]); + int maxIdx = k; + for (int j = k + 1; j < matrix.length; j++) { + if (Math.abs(matrix[j][k]) > maxVal) { + maxVal = matrix[j][k]; + maxIdx = j; + } + } + if (Math.abs(maxVal) < tol) { + // hope the matrix works out + continue; + } + // swap rows + double[] temp = matrix[k]; + matrix[k] = matrix[maxIdx]; + matrix[maxIdx] = temp; + double tempConst = constants[k]; + constants[k] = constants[maxIdx]; + constants[maxIdx] = tempConst; + for (int i = k + 1; i < matrix.length; i++) { + // compute multipliers and save them in the column + matrix[i][k] /= matrix[k][k]; + for (int j = k + 1; j < matrix.length; j++) { + matrix[i][j] -= matrix[i][k] * matrix[k][j]; + } + constants[i] -= matrix[i][k] * constants[k]; + } + } + // back substitution + double[] x = new double[constants.length]; + System.arraycopy(constants, 0, x, 0, constants.length); + for (int i = matrix.length - 1; i >= 0; i--) { + double sum = 0; + for (int j = i + 1; j < matrix.length; j++) { + sum += matrix[i][j] * x[j]; + } + x[i] = constants[i] - sum; + if (Math.abs(matrix[i][i]) > tol) { + x[i] /= matrix[i][i]; + } else { + throw new IllegalArgumentException("Matrix was found to be singular"); + } + } + return x; + } +} diff --git a/src/main/java/com/thealgorithms/matrix/matrixexponentiation/Fibonacci.java b/src/main/java/com/thealgorithms/matrix/matrixexponentiation/Fibonacci.java new file mode 100644 index 000000000000..85852713b9ba --- /dev/null +++ b/src/main/java/com/thealgorithms/matrix/matrixexponentiation/Fibonacci.java @@ -0,0 +1,42 @@ +package com.thealgorithms.matrix.matrixexponentiation; + +import com.thealgorithms.matrix.utils.MatrixUtil; +import java.math.BigDecimal; + +/** + * @author Anirudh Buvanesh (https://github.com/anirudhb11) For more information + * see https://www.geeksforgeeks.org/matrix-exponentiation/ + * + */ +public final class Fibonacci { + private Fibonacci() { + } + + // Exponentiation matrix for Fibonacci sequence + private static final BigDecimal ONE = BigDecimal.valueOf(1); + private static final BigDecimal ZERO = BigDecimal.valueOf(0); + + private static final BigDecimal[][] FIB_MATRIX = {{ONE, ONE}, {ONE, ZERO}}; + private static final BigDecimal[][] IDENTITY_MATRIX = {{ONE, ZERO}, {ZERO, ONE}}; + + /** + * Calculates the fibonacci number using matrix exponentiaition technique + * + * @param n The input n for which we have to determine the fibonacci number + * Outputs the nth * fibonacci number + * @return a 2 X 1 array as { {F_n+1}, {F_n} } + */ + public static BigDecimal[][] fib(int n) { + if (n == 0) { + return IDENTITY_MATRIX; + } else { + BigDecimal[][] cachedResult = fib(n / 2); + BigDecimal[][] matrixExpResult = MatrixUtil.multiply(cachedResult, cachedResult).get(); + if (n % 2 == 0) { + return matrixExpResult; + } else { + return MatrixUtil.multiply(FIB_MATRIX, matrixExpResult).get(); + } + } + } +} diff --git a/src/main/java/com/thealgorithms/maths/MatrixUtil.java b/src/main/java/com/thealgorithms/matrix/utils/MatrixUtil.java similarity index 62% rename from src/main/java/com/thealgorithms/maths/MatrixUtil.java rename to src/main/java/com/thealgorithms/matrix/utils/MatrixUtil.java index 7e462f92e185..5ff9e37f6b9a 100644 --- a/src/main/java/com/thealgorithms/maths/MatrixUtil.java +++ b/src/main/java/com/thealgorithms/matrix/utils/MatrixUtil.java @@ -1,4 +1,4 @@ -package com.thealgorithms.maths; +package com.thealgorithms.matrix.utils; import java.math.BigDecimal; import java.util.Optional; @@ -10,6 +10,7 @@ * @date: 31 October 2021 (Sunday) */ public final class MatrixUtil { + private MatrixUtil() { } @@ -18,11 +19,52 @@ private static boolean isValid(final BigDecimal[][] matrix) { } private static boolean hasEqualSizes(final BigDecimal[][] matrix1, final BigDecimal[][] matrix2) { - return (isValid(matrix1) && isValid(matrix2) && matrix1.length == matrix2.length && matrix1[0].length == matrix2[0].length); + return isValid(matrix1) && isValid(matrix2) && matrix1.length == matrix2.length && matrix1[0].length == matrix2[0].length; } private static boolean canMultiply(final BigDecimal[][] matrix1, final BigDecimal[][] matrix2) { - return (isValid(matrix1) && isValid(matrix2) && matrix1[0].length == matrix2.length); + return isValid(matrix1) && isValid(matrix2) && matrix1[0].length == matrix2.length; + } + + public 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; + } + + /** + * @brief Checks if the input matrix is a jagged matrix. + * Jagged matrix is a matrix where the number of columns in each row is not the same. + * + * @param matrix The input matrix + * @return True if the input matrix is a jagged matrix, false otherwise + */ + private static boolean isJaggedMatrix(double[][] matrix) { + int numColumns = matrix[0].length; + for (double[] row : matrix) { + if (row.length != numColumns) { + return true; + } + } + return false; } private static Optional operate(final BigDecimal[][] matrix1, final BigDecimal[][] matrix2, final BiFunction operation) { @@ -80,4 +122,12 @@ public static Optional multiply(final BigDecimal[][] matrix1, fi return Optional.of(result); } + + public static double[] reverseRow(final double[] inRow) { + double[] res = new double[inRow.length]; + for (int i = 0; i < inRow.length; ++i) { + res[i] = inRow[inRow.length - 1 - i]; + } + return res; + } } diff --git a/src/main/java/com/thealgorithms/matrixexponentiation/Fibonacci.java b/src/main/java/com/thealgorithms/matrixexponentiation/Fibonacci.java deleted file mode 100644 index afd34933047a..000000000000 --- a/src/main/java/com/thealgorithms/matrixexponentiation/Fibonacci.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.thealgorithms.matrixexponentiation; - -import java.util.Scanner; - -/** - * @author Anirudh Buvanesh (https://github.com/anirudhb11) For more information - * see https://www.geeksforgeeks.org/matrix-exponentiation/ - * - */ -public final class Fibonacci { - private Fibonacci() { - } - - // Exponentiation matrix for Fibonacci sequence - private static final int[][] FIB_MATRIX = {{1, 1}, {1, 0}}; - private static final int[][] IDENTITY_MATRIX = {{1, 0}, {0, 1}}; - // First 2 fibonacci numbers - private static final int[][] BASE_FIB_NUMBERS = {{1}, {0}}; - - /** - * Performs multiplication of 2 matrices - * - * @param matrix1 - * @param matrix2 - * @return The product of matrix1 and matrix2 - */ - private static int[][] matrixMultiplication(int[][] matrix1, int[][] matrix2) { - // Check if matrices passed can be multiplied - int rowsInMatrix1 = matrix1.length; - int columnsInMatrix1 = matrix1[0].length; - - int rowsInMatrix2 = matrix2.length; - int columnsInMatrix2 = matrix2[0].length; - - assert columnsInMatrix1 == rowsInMatrix2; - int[][] product = new int[rowsInMatrix1][columnsInMatrix2]; - for (int rowIndex = 0; rowIndex < rowsInMatrix1; rowIndex++) { - for (int colIndex = 0; colIndex < columnsInMatrix2; colIndex++) { - int matrixEntry = 0; - for (int intermediateIndex = 0; intermediateIndex < columnsInMatrix1; intermediateIndex++) { - matrixEntry += matrix1[rowIndex][intermediateIndex] * matrix2[intermediateIndex][colIndex]; - } - product[rowIndex][colIndex] = matrixEntry; - } - } - return product; - } - - /** - * Calculates the fibonacci number using matrix exponentiaition technique - * - * @param n The input n for which we have to determine the fibonacci number - * Outputs the nth * fibonacci number - * @return a 2 X 1 array as { {F_n+1}, {F_n} } - */ - public static int[][] fib(int n) { - if (n == 0) { - return Fibonacci.IDENTITY_MATRIX; - } else { - int[][] cachedResult = fib(n / 2); - int[][] matrixExpResult = matrixMultiplication(cachedResult, cachedResult); - if (n % 2 == 0) { - return matrixExpResult; - } else { - return matrixMultiplication(Fibonacci.FIB_MATRIX, matrixExpResult); - } - } - } - - public static void main(String[] args) { - // Returns [0, 1, 1, 2, 3, 5 ..] for n = [0, 1, 2, 3, 4, 5.. ] - Scanner sc = new Scanner(System.in); - int n = sc.nextInt(); - int[][] result = matrixMultiplication(fib(n), BASE_FIB_NUMBERS); - System.out.println("Fib(" + n + ") = " + result[1][0]); - sc.close(); - } -} diff --git a/src/main/java/com/thealgorithms/misc/MatrixTranspose.java b/src/main/java/com/thealgorithms/misc/MatrixTranspose.java deleted file mode 100644 index 153cf4e9df99..000000000000 --- a/src/main/java/com/thealgorithms/misc/MatrixTranspose.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.thealgorithms.misc; - -import java.util.Scanner; - -/** - * - * - *

Find the Transpose of Matrix!

- * - * Simply take input from the user and print the matrix before the transpose and - * after the transpose. - * - *

- * Note: Giving proper comments in your program makes it more user - * friendly and it is assumed as a high quality code. - * - * @author Rajat-Jain29 - * @version 11.0.9 - * @since 2014-03-31 - */ -public final class MatrixTranspose { - private MatrixTranspose() { - } - - public static void main(String[] args) { - /* - * This is the main method - * - * @param args Unused. - * - * @return Nothing. - */ - Scanner sc = new Scanner(System.in); - int i; - int j; - int row; - int column; - System.out.println("Enter the number of rows in the 2D matrix:"); - - /* - * Take input from user for how many rows to be print - */ - row = sc.nextInt(); - - System.out.println("Enter the number of columns in the 2D matrix:"); - - /* - * Take input from user for how many coloumn to be print - */ - column = sc.nextInt(); - int[][] arr = new int[row][column]; - System.out.println("Enter the elements"); - for (i = 0; i < row; i++) { - for (j = 0; j < column; j++) { - arr[i][j] = sc.nextInt(); - } - } - - /* - * Print matrix before the Transpose in proper way - */ - System.out.println("The matrix is:"); - for (i = 0; i < row; i++) { - for (j = 0; j < column; j++) { - System.out.print(arr[i][j] + "\t"); - } - System.out.print("\n"); - } - - /* - * Print matrix after the tranpose in proper way Transpose means Interchanging - * of rows wth column so we interchange the rows in next loop Thus at last - * matrix of transpose is obtained through user input... - */ - System.out.println("The Transpose of the given matrix is:"); - for (i = 0; i < column; i++) { - for (j = 0; j < row; j++) { - System.out.print(arr[j][i] + "\t"); - } - System.out.print("\n"); - } - } -} diff --git a/src/main/java/com/thealgorithms/misc/MirrorOfMatrix.java b/src/main/java/com/thealgorithms/misc/MirrorOfMatrix.java deleted file mode 100644 index 89dfce3fe049..000000000000 --- a/src/main/java/com/thealgorithms/misc/MirrorOfMatrix.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.thealgorithms.misc; - -// Problem Statement -/* -We have given an array of m x n (where m is the number of rows and n is the number of columns). -Print the new matrix in such a way that the new matrix is the mirror image of the original matrix. - -The Original matrix is: | The Mirror matrix is: -1 2 3 | 3 2 1 -4 5 6 | 6 5 4 -7 8 9 | 9 8 7 - -@author - Aman (https://github.com/Aman28801) -*/ - -public final class MirrorOfMatrix { - private MirrorOfMatrix() { - } - - public static int[][] mirrorMatrix(final int[][] originalMatrix) { - if (originalMatrix == null) { - // Handle invalid input - return null; - } - if (originalMatrix.length == 0) { - return new int[0][0]; - } - - checkInput(originalMatrix); - - int numRows = originalMatrix.length; - int numCols = originalMatrix[0].length; - - int[][] mirroredMatrix = new int[numRows][numCols]; - - for (int i = 0; i < numRows; i++) { - mirroredMatrix[i] = reverseRow(originalMatrix[i]); - } - return mirroredMatrix; - } - private static int[] reverseRow(final int[] inRow) { - int[] res = new int[inRow.length]; - for (int i = 0; i < inRow.length; ++i) { - res[i] = inRow[inRow.length - 1 - i]; - } - return res; - } - - private static void checkInput(final int[][] matrix) { - // Check if all rows have the same number of columns - for (int i = 1; i < matrix.length; i++) { - if (matrix[i].length != matrix[0].length) { - throw new IllegalArgumentException("The input is not a matrix."); - } - } - } -} diff --git a/src/main/java/com/thealgorithms/misc/PalindromePrime.java b/src/main/java/com/thealgorithms/misc/PalindromePrime.java index e1cbf3ff839a..164e957a9d12 100644 --- a/src/main/java/com/thealgorithms/misc/PalindromePrime.java +++ b/src/main/java/com/thealgorithms/misc/PalindromePrime.java @@ -1,51 +1,57 @@ package com.thealgorithms.misc; -import java.util.Scanner; +import java.util.ArrayList; +import java.util.List; public final class PalindromePrime { private PalindromePrime() { } - public static void main(String[] args) { // Main function - Scanner in = new Scanner(System.in); - System.out.println("Enter the quantity of First Palindromic Primes you want"); - int n = in.nextInt(); // Input of how many first palindromic prime we want - functioning(n); // calling function - functioning - in.close(); - } + public static boolean prime(int num) { + if (num < 2) { + return false; // Handle edge case for numbers < 2 + } + if (num == 2) { + return true; // 2 is prime + } + if (num % 2 == 0) { + return false; // Even numbers > 2 are not prime + } - public static boolean prime(int num) { // checking if number is prime or not for (int divisor = 3; divisor <= Math.sqrt(num); divisor += 2) { if (num % divisor == 0) { - return false; // false if not prime + return false; } } - return true; // True if prime + return true; } - public static int reverse(int n) { // Returns the reverse of the number + public static int reverse(int n) { int reverse = 0; while (n != 0) { - reverse *= 10; - reverse += n % 10; + reverse = reverse * 10 + (n % 10); n /= 10; } return reverse; } - public static void functioning(int y) { - if (y == 0) { - return; + public static List generatePalindromePrimes(int n) { + List palindromicPrimes = new ArrayList<>(); + if (n <= 0) { + return palindromicPrimes; // Handle case for 0 or negative input } - System.out.print(2 + "\n"); // print the first Palindromic Prime + + palindromicPrimes.add(2); // 2 is the first palindromic prime int count = 1; int num = 3; - while (count < y) { - if (num == reverse(num) && prime(num)) { // number is prime and it's reverse is same - count++; // counts check when to terminate while loop - System.out.print(num + "\n"); // print the Palindromic Prime + + while (count < n) { + if (num == reverse(num) && prime(num)) { + palindromicPrimes.add(num); + count++; } - num += 2; // inrease iterator value by two + num += 2; // Skip even numbers } + return palindromicPrimes; } } diff --git a/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java b/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java index 07286b39f2e4..51dc099ba32e 100644 --- a/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java +++ b/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java @@ -1,6 +1,5 @@ package com.thealgorithms.misc; -import com.thealgorithms.datastructures.lists.SinglyLinkedList; import java.util.Stack; /** @@ -15,8 +14,8 @@ public final class PalindromeSinglyLinkedList { private PalindromeSinglyLinkedList() { } - public static boolean isPalindrome(final SinglyLinkedList linkedList) { - Stack linkedListValues = new Stack<>(); + public static boolean isPalindrome(final Iterable linkedList) { + var linkedListValues = new Stack<>(); for (final var x : linkedList) { linkedListValues.push(x); @@ -30,4 +29,47 @@ public static boolean isPalindrome(final SinglyLinkedList linkedList) { return true; } + + // Optimised approach with O(n) time complexity and O(1) space complexity + + public static boolean isPalindromeOptimised(Node head) { + if (head == null || head.next == null) { + return true; + } + Node slow = head; + Node fast = head; + while (fast != null && fast.next != null) { + slow = slow.next; + fast = fast.next.next; + } + Node midNode = slow; + + Node prevNode = null; + Node currNode = midNode; + Node nextNode; + while (currNode != null) { + nextNode = currNode.next; + currNode.next = prevNode; + prevNode = currNode; + currNode = nextNode; + } + Node left = head; + Node right = prevNode; + while (left != null && right != null) { + if (left.val != right.val) { + return false; + } + right = right.next; + left = left.next; + } + return true; + } + static class Node { + int val; + Node next; + Node(int val) { + this.val = val; + this.next = null; + } + } } diff --git a/src/main/java/com/thealgorithms/misc/RangeInSortedArray.java b/src/main/java/com/thealgorithms/misc/RangeInSortedArray.java index 0dfc8ac32a6f..6d3caa1814b6 100644 --- a/src/main/java/com/thealgorithms/misc/RangeInSortedArray.java +++ b/src/main/java/com/thealgorithms/misc/RangeInSortedArray.java @@ -1,18 +1,9 @@ package com.thealgorithms.misc; -import java.util.Arrays; - public final class RangeInSortedArray { private RangeInSortedArray() { } - public static void main(String[] args) { - // Testcases - assert Arrays.equals(sortedRange(new int[] {1, 2, 3, 3, 3, 4, 5}, 3), new int[] {2, 4}); - assert Arrays.equals(sortedRange(new int[] {1, 2, 3, 3, 3, 4, 5}, 4), new int[] {5, 5}); - assert Arrays.equals(sortedRange(new int[] {0, 1, 2}, 3), new int[] {-1, -1}); - } - // Get the 1st and last occurrence index of a number 'key' in a non-decreasing array 'nums' // Gives [-1, -1] in case element doesn't exist in array public static int[] sortedRange(int[] nums, int key) { diff --git a/src/main/java/com/thealgorithms/misc/ShuffleArray.java b/src/main/java/com/thealgorithms/misc/ShuffleArray.java new file mode 100644 index 000000000000..65d38adfc37a --- /dev/null +++ b/src/main/java/com/thealgorithms/misc/ShuffleArray.java @@ -0,0 +1,38 @@ +package com.thealgorithms.misc; + +import java.util.Random; + +/** + * The Fisher-Yates (Knuth) Shuffle algorithm randomly permutes an array's + * elements, ensuring each permutation is equally likely. + * + *

+ * Worst-case performance O(n) + * Best-case performance O(n) + * Average performance O(n) + * Worst-case space complexity O(1) + * + * This class provides a static method to shuffle an array in place. + * + * @author Rashi Dashore (https://github.com/rashi07dashore) + */ +public final class ShuffleArray { + // Prevent instantiation + private ShuffleArray() { + } + + /** + * This method shuffles an array using the Fisher-Yates algorithm. + * + * @param arr is the input array to be shuffled + */ + public static void shuffle(int[] arr) { + Random random = new Random(); + for (int i = arr.length - 1; i > 0; i--) { + int j = random.nextInt(i + 1); + int temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; + } + } +} diff --git a/src/main/java/com/thealgorithms/misc/Sparsity.java b/src/main/java/com/thealgorithms/misc/Sparsity.java index cae2fbdead94..08e50a121da4 100644 --- a/src/main/java/com/thealgorithms/misc/Sparsity.java +++ b/src/main/java/com/thealgorithms/misc/Sparsity.java @@ -1,7 +1,5 @@ package com.thealgorithms.misc; -import java.util.Scanner; - /* *A matrix is sparse if many of its coefficients are zero (In general if 2/3rd of matrix elements *are 0, it is considered as sparse). The interest in sparsity arises because its exploitation can @@ -16,12 +14,17 @@ private Sparsity() { } /* + * @param mat the input matrix * @return Sparsity of matrix * * where sparsity = number of zeroes/total elements in matrix * */ static double sparsity(double[][] mat) { + if (mat == null || mat.length == 0) { + throw new IllegalArgumentException("Matrix cannot be null or empty"); + } + int zero = 0; // Traversing the matrix to count number of zeroes for (int i = 0; i < mat.length; i++) { @@ -32,25 +35,6 @@ static double sparsity(double[][] mat) { } } // return sparsity - return ((double) zero / (mat.length * mat[1].length)); - } - - // Driver method - public static void main(String[] args) { - Scanner in = new Scanner(System.in); - System.out.println("Enter number of rows in matrix: "); - int n = in.nextInt(); - System.out.println("Enter number of Columns in matrix: "); - int m = in.nextInt(); - - System.out.println("Enter Matrix elements: "); - double[][] mat = new double[n][m]; - for (int i = 0; i < n; i++) { - for (int j = 0; j < m; j++) { - mat[i][j] = in.nextDouble(); - } - } - System.out.println("Sparsity of matrix is: " + sparsity(mat)); - in.close(); + return ((double) zero / (mat.length * mat[0].length)); } } diff --git a/src/main/java/com/thealgorithms/misc/ThreeSumProblem.java b/src/main/java/com/thealgorithms/misc/ThreeSumProblem.java index 1c5f4a440532..8ef10758ef80 100644 --- a/src/main/java/com/thealgorithms/misc/ThreeSumProblem.java +++ b/src/main/java/com/thealgorithms/misc/ThreeSumProblem.java @@ -7,29 +7,10 @@ import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; -import java.util.Scanner; import java.util.Set; public class ThreeSumProblem { - public static void main(String[] args) { - Scanner scan = new Scanner(System.in); - System.out.print("Enter the target sum "); - int ts = scan.nextInt(); - System.out.print("Enter the number of elements in the array "); - int n = scan.nextInt(); - System.out.println("Enter all your array elements:"); - int[] arr = new int[n]; - for (int i = 0; i < n; i++) { - arr[i] = scan.nextInt(); - } - ThreeSumProblem th = new ThreeSumProblem(); - System.out.println("Brute Force Approach\n" + (th.bruteForce(arr, ts)) + "\n"); - System.out.println("Two Pointer Approach\n" + (th.twoPointer(arr, ts)) + "\n"); - System.out.println("Hashmap Approach\n" + (th.hashMap(arr, ts))); - scan.close(); - } - public List> bruteForce(int[] nums, int target) { List> arr = new ArrayList>(); diff --git a/src/main/java/com/thealgorithms/others/CRCAlgorithm.java b/src/main/java/com/thealgorithms/others/CRCAlgorithm.java index bfa8828e250b..284a290a5af8 100644 --- a/src/main/java/com/thealgorithms/others/CRCAlgorithm.java +++ b/src/main/java/com/thealgorithms/others/CRCAlgorithm.java @@ -25,8 +25,6 @@ public class CRCAlgorithm { private ArrayList message; - private ArrayList dividedMessage; - private ArrayList p; private Random randomGenerator; @@ -44,7 +42,6 @@ public CRCAlgorithm(String str, int size, double ber) { messageChanged = false; message = new ArrayList<>(); messSize = size; - dividedMessage = new ArrayList<>(); p = new ArrayList<>(); for (int i = 0; i < str.length(); i++) { p.add(Character.getNumericValue(str.charAt(i))); @@ -103,7 +100,6 @@ public int getCorrectMess() { public void refactor() { messageChanged = false; message = new ArrayList<>(); - dividedMessage = new ArrayList<>(); } /** @@ -156,7 +152,7 @@ public void divideMessageWithP(boolean check) { } } } - dividedMessage = (ArrayList) x.clone(); + ArrayList dividedMessage = (ArrayList) x.clone(); if (!check) { message.addAll(dividedMessage); } else { diff --git a/src/main/java/com/thealgorithms/others/FibbonaciSeries.java b/src/main/java/com/thealgorithms/others/FibbonaciSeries.java deleted file mode 100644 index a4815296e547..000000000000 --- a/src/main/java/com/thealgorithms/others/FibbonaciSeries.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.thealgorithms.others; - -import java.util.Scanner; - -/** - * Fibonacci sequence, and characterized by the fact that every number after the - * first two is the sum of the two preceding ones. - * - *

- * Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21,... - * - *

- * Source for the explanation: https://en.wikipedia.org/wiki/Fibonacci_number - * - * Problem Statement: print all Fibonacci numbers that are smaller than your - * given input N - */ -public final class FibbonaciSeries { - private FibbonaciSeries() { - } - - public static void main(String[] args) { - // Get input from the user - Scanner scan = new Scanner(System.in); - int n = scan.nextInt(); - int first = 0; - int second = 1; - scan.close(); - while (first <= n) { - // print first fibo 0 then add second fibo into it while updating second as well - System.out.println(first); - int next = first + second; - first = second; - second = next; - } - } -} diff --git a/src/main/java/com/thealgorithms/others/KochSnowflake.java b/src/main/java/com/thealgorithms/others/KochSnowflake.java index 0e2600a7d72f..10986aabec4f 100644 --- a/src/main/java/com/thealgorithms/others/KochSnowflake.java +++ b/src/main/java/com/thealgorithms/others/KochSnowflake.java @@ -7,6 +7,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.List; import javax.imageio.ImageIO; /** @@ -104,7 +105,7 @@ public static BufferedImage getKochSnowflake(int imageWidth, int steps) { double offsetX = imageWidth / 10.; double offsetY = imageWidth / 3.7; Vector2 vector1 = new Vector2(offsetX, offsetY); - Vector2 vector2 = new Vector2(imageWidth / 2, Math.sin(Math.PI / 3) * imageWidth * 0.8 + offsetY); + Vector2 vector2 = new Vector2(imageWidth / 2.0, Math.sin(Math.PI / 3.0) * imageWidth * 0.8 + offsetY); Vector2 vector3 = new Vector2(imageWidth - offsetX, offsetY); ArrayList initialVectors = new ArrayList(); initialVectors.add(vector1); @@ -125,7 +126,7 @@ public static BufferedImage getKochSnowflake(int imageWidth, int steps) { * applied. * @return The transformed vectors after the iteration-step. */ - private static ArrayList iterationStep(ArrayList vectors) { + private static ArrayList iterationStep(List vectors) { ArrayList newVectors = new ArrayList(); for (int i = 0; i < vectors.size() - 1; i++) { Vector2 startVector = vectors.get(i); diff --git a/src/main/java/com/thealgorithms/others/MaximumSlidingWindow.java b/src/main/java/com/thealgorithms/others/MaximumSlidingWindow.java new file mode 100644 index 000000000000..d0b2c2a0e56d --- /dev/null +++ b/src/main/java/com/thealgorithms/others/MaximumSlidingWindow.java @@ -0,0 +1,56 @@ +package com.thealgorithms.others; + +import java.util.ArrayDeque; +import java.util.Deque; + +/** + * Maximum Sliding Window Algorithm + * + * This algorithm finds the maximum element in each sliding window of size k + * in a given array of integers. It uses a deque (double-ended queue) to + * efficiently keep track of potential maximum values in the current window. + * + * Time Complexity: O(n), where n is the number of elements in the input array + * Space Complexity: O(k), where k is the size of the sliding window + */ + +public class MaximumSlidingWindow { + + /** + * Finds the maximum values in each sliding window of size k. + * + * @param nums The input array of integers + * @param windowSize The size of the sliding window + * @return An array of integers representing the maximums in each window + */ + public int[] maxSlidingWindow(int[] nums, int windowSize) { + if (nums == null || nums.length == 0 || windowSize <= 0 || windowSize > nums.length) { + return new int[0]; // Handle edge cases + } + + int[] result = new int[nums.length - windowSize + 1]; + Deque deque = new ArrayDeque<>(); + + for (int currentIndex = 0; currentIndex < nums.length; currentIndex++) { + + // Remove the first element if it's outside the current window + if (!deque.isEmpty() && deque.peekFirst() == currentIndex - windowSize) { + deque.pollFirst(); + } + + // Remove all elements smaller than the current element from the end + while (!deque.isEmpty() && nums[deque.peekLast()] < nums[currentIndex]) { + deque.pollLast(); + } + + // Add the current element's index to the deque + deque.offerLast(currentIndex); + + // If we have processed at least k elements, add to result + if (currentIndex >= windowSize - 1) { + result[currentIndex - windowSize + 1] = nums[deque.peekFirst()]; + } + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/others/PrintAMatrixInSpiralOrder.java b/src/main/java/com/thealgorithms/others/PrintAMatrixInSpiralOrder.java index ddc37a916cbf..abfdd006879e 100644 --- a/src/main/java/com/thealgorithms/others/PrintAMatrixInSpiralOrder.java +++ b/src/main/java/com/thealgorithms/others/PrintAMatrixInSpiralOrder.java @@ -12,43 +12,33 @@ public class PrintAMatrixInSpiralOrder { * @param col number of columns matrix has * @author Sadiul Hakim : https://github.com/sadiul-hakim */ - public List print(int[][] matrix, int row, int col) { - // r traverses matrix row wise from first int r = 0; // c traverses matrix column wise from first int c = 0; int i; - List result = new ArrayList<>(); - while (r < row && c < col) { // print first row of matrix for (i = c; i < col; i++) { result.add(matrix[r][i]); } - // increase r by one because first row printed r++; - // print last column for (i = r; i < row; i++) { result.add(matrix[i][col - 1]); } - // decrease col by one because last column has been printed col--; - // print rows from last except printed elements if (r < row) { for (i = col - 1; i >= c; i--) { result.add(matrix[row - 1][i]); } - row--; } - // print columns from first except printed elements if (c < col) { for (i = row - 1; i >= r; i--) { diff --git a/src/main/java/com/thealgorithms/others/TowerOfHanoi.java b/src/main/java/com/thealgorithms/others/TowerOfHanoi.java deleted file mode 100644 index 2216799b987a..000000000000 --- a/src/main/java/com/thealgorithms/others/TowerOfHanoi.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.thealgorithms.others; - -import java.util.Scanner; - -final class TowerOfHanoi { - private TowerOfHanoi() { - } - - public static void shift(int n, String startPole, String intermediatePole, String endPole) { - // if n becomes zero the program returns thus ending the loop. - if (n != 0) { - // Shift function is called in recursion for swapping the n-1 disc from the startPole to - // the intermediatePole - shift(n - 1, startPole, endPole, intermediatePole); - System.out.format("Move %d from %s to %s%n", n, startPole, endPole); // Result Printing - // Shift function is called in recursion for swapping the n-1 disc from the - // intermediatePole to the endPole - shift(n - 1, intermediatePole, startPole, endPole); - } - } - - public static void main(String[] args) { - System.out.print("Enter number of discs on Pole 1: "); - Scanner scanner = new Scanner(System.in); - int numberOfDiscs = scanner.nextInt(); // input of number of discs on pole 1 - shift(numberOfDiscs, "Pole1", "Pole2", "Pole3"); // Shift function called - scanner.close(); - } -} diff --git a/src/main/java/com/thealgorithms/others/Sudoku.java b/src/main/java/com/thealgorithms/puzzlesandgames/Sudoku.java similarity index 99% rename from src/main/java/com/thealgorithms/others/Sudoku.java rename to src/main/java/com/thealgorithms/puzzlesandgames/Sudoku.java index 0e88aee46f4d..fce665c4de00 100644 --- a/src/main/java/com/thealgorithms/others/Sudoku.java +++ b/src/main/java/com/thealgorithms/puzzlesandgames/Sudoku.java @@ -1,4 +1,4 @@ -package com.thealgorithms.others; +package com.thealgorithms.puzzlesandgames; /** * A class that provides methods to solve Sudoku puzzles of any n x n size diff --git a/src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java b/src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java new file mode 100644 index 000000000000..72e9a14ac070 --- /dev/null +++ b/src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java @@ -0,0 +1,65 @@ +package com.thealgorithms.puzzlesandgames; + +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: + * 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. + * + *

+ * For more information about the Tower of Hanoi, see + * Tower of Hanoi on Wikipedia. + *

+ * + * 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 + */ +final class TowerOfHanoi { + + private TowerOfHanoi() { + } + + /** + * Recursively solve the Tower of Hanoi puzzle by moving discs between poles. + * + * @param n The number of discs to move. + * @param startPole The name of the start pole from which discs are moved. + * @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. + * + *

+ * This method is called recursively to move n-1 discs + * to the intermediate pole, + * then moves the nth disc to the end pole, and finally + * moves the n-1 discs from the + * intermediate pole to the end pole. + *

+ * + *

+ * 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. + *

+ */ + 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); + + // 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/misc/WordBoggle.java b/src/main/java/com/thealgorithms/puzzlesandgames/WordBoggle.java similarity index 75% rename from src/main/java/com/thealgorithms/misc/WordBoggle.java rename to src/main/java/com/thealgorithms/puzzlesandgames/WordBoggle.java index 3eb0dc95ffb5..ca1430f744ab 100644 --- a/src/main/java/com/thealgorithms/misc/WordBoggle.java +++ b/src/main/java/com/thealgorithms/puzzlesandgames/WordBoggle.java @@ -1,7 +1,6 @@ -package com.thealgorithms.misc; +package com.thealgorithms.puzzlesandgames; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -9,9 +8,9 @@ import java.util.Set; public final class WordBoggle { + private WordBoggle() { } - /** * O(nm * 8^s + ws) time where n = width of boggle board, m = height of * boggle board, s = length of longest word in string array, w = length of @@ -32,36 +31,6 @@ public static List boggleBoard(char[][] board, String[] words) { return new ArrayList<>(finalWords); } - public static void main(String[] args) { - // Testcase - List ans = new ArrayList<>(Arrays.asList("a", "boggle", "this", "NOTRE_PEATED", "is", "simple", "board")); - assert (boggleBoard( - new char[][] { - {'t', 'h', 'i', 's', 'i', 's', 'a'}, - {'s', 'i', 'm', 'p', 'l', 'e', 'x'}, - {'b', 'x', 'x', 'x', 'x', 'e', 'b'}, - {'x', 'o', 'g', 'g', 'l', 'x', 'o'}, - {'x', 'x', 'x', 'D', 'T', 'r', 'a'}, - {'R', 'E', 'P', 'E', 'A', 'd', 'x'}, - {'x', 'x', 'x', 'x', 'x', 'x', 'x'}, - {'N', 'O', 'T', 'R', 'E', '_', 'P'}, - {'x', 'x', 'D', 'E', 'T', 'A', 'E'}, - }, - new String[] { - "this", - "is", - "not", - "a", - "simple", - "test", - "boggle", - "board", - "REPEATED", - "NOTRE_PEATED", - }) - .equals(ans)); - } - public static void explore(int i, int j, char[][] board, TrieNode trieNode, boolean[][] visited, Set finalWords) { if (visited[i][j]) { return; diff --git a/src/main/java/com/thealgorithms/randomized/KargerMinCut.java b/src/main/java/com/thealgorithms/randomized/KargerMinCut.java new file mode 100644 index 000000000000..14f1f97450a0 --- /dev/null +++ b/src/main/java/com/thealgorithms/randomized/KargerMinCut.java @@ -0,0 +1,195 @@ +package com.thealgorithms.randomized; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +/** + * Implementation of Karger's Minimum Cut algorithm. + * + *

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

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

Key steps of the algorithm: + *

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

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

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

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

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

For a one-dimensional integral over [a, b], the approximation is the function range (b-a), + * multiplied by the function average result for a random sample. + * See more: Monte Carlo Integration + * + * @author: MuhammadEzzatHBK + */ + +public final class MonteCarloIntegration { + + private MonteCarloIntegration() { + } + + /** + * Approximates the definite integral of a given function over a specified + * interval using the Monte Carlo method with a fixed random seed for + * reproducibility. + * + * @param fx the function to integrate + * @param a the lower bound of the interval + * @param b the upper bound of the interval + * @param n the number of random samples to use + * @param seed the seed for the random number generator + * @return the approximate value of the integral + */ + public static double approximate(Function fx, double a, double b, int n, long seed) { + return doApproximate(fx, a, b, n, new Random(seed)); + } + + /** + * Approximates the definite integral of a given function over a specified + * interval using the Monte Carlo method with a random seed based on the + * current system time for more randomness. + * + * @param fx the function to integrate + * @param a the lower bound of the interval + * @param b the upper bound of the interval + * @param n the number of random samples to use + * @return the approximate value of the integral + */ + public static double approximate(Function fx, double a, double b, int n) { + return doApproximate(fx, a, b, n, new Random(System.currentTimeMillis())); + } + + private static double doApproximate(Function fx, double a, double b, int n, Random generator) { + if (!validate(fx, a, b, n)) { + throw new IllegalArgumentException("Invalid input parameters"); + } + double totalArea = 0.0; + double interval = b - a; + for (int i = 0; i < n; i++) { + double x = a + generator.nextDouble() * interval; + totalArea += fx.apply(x); + } + return interval * totalArea / n; + } + + private static boolean validate(Function fx, double a, double b, int n) { + boolean isFunctionValid = fx != null; + boolean isIntervalValid = a < b; + boolean isSampleSizeValid = n > 0; + return isFunctionValid && isIntervalValid && isSampleSizeValid; + } +} diff --git a/src/main/java/com/thealgorithms/randomized/RandomizedQuickSort.java b/src/main/java/com/thealgorithms/randomized/RandomizedQuickSort.java new file mode 100644 index 000000000000..e9af223a0622 --- /dev/null +++ b/src/main/java/com/thealgorithms/randomized/RandomizedQuickSort.java @@ -0,0 +1,68 @@ +package com.thealgorithms.randomized; + +/** + * This class implements the Randomized QuickSort algorithm. + * It selects a pivot randomly to improve performance on sorted or nearly sorted data. + * @author Vibhu Khera + */ +public final class RandomizedQuickSort { + + private RandomizedQuickSort() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * Sorts the array using the randomized quicksort algorithm. + * + * @param arr the array to sort + * @param low the starting index of the array + * @param high the ending index of the array + */ + public static void randomizedQuickSort(int[] arr, int low, int high) { + if (low < high) { + int pivotIndex = partition(arr, low, high); + randomizedQuickSort(arr, low, pivotIndex - 1); + randomizedQuickSort(arr, pivotIndex + 1, high); + } + } + + /** + * Partitions the array around a pivot chosen randomly. + * + * @param arr the array to partition + * @param low the starting index + * @param high the ending index + * @return the index of the pivot after partitioning + */ + private static int partition(int[] arr, int low, int high) { + int pivotIndex = low + (int) (Math.random() * (high - low + 1)); + int pivotValue = arr[pivotIndex]; + swap(arr, pivotIndex, high); // Move pivot to end + int storeIndex = low; + for (int i = low; i < high; i++) { + if (arr[i] < pivotValue) { + swap(arr, storeIndex, i); + storeIndex++; + } + } + swap(arr, storeIndex, high); // Move pivot to its final place + return storeIndex; + } + + /** + * Swaps two elements in the array, only if the indices are different. + * + * @param arr the array in which elements are to be swapped + * @param i the first index + * @param j the second index + */ + private static void swap(int[] arr, int i, int j) { + // Skip if indices are the same + if (i == j) { + return; + } + int temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; + } +} diff --git a/src/main/java/com/thealgorithms/randomized/ReservoirSampling.java b/src/main/java/com/thealgorithms/randomized/ReservoirSampling.java new file mode 100644 index 000000000000..05e70f635055 --- /dev/null +++ b/src/main/java/com/thealgorithms/randomized/ReservoirSampling.java @@ -0,0 +1,55 @@ +package com.thealgorithms.randomized; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * Reservoir Sampling Algorithm + * + * Use Case: + * - Efficient for selecting k random items from a stream of unknown size + * - Used in streaming systems, big data, and memory-limited environments + * + * Time Complexity: O(n) + * Space Complexity: O(k) + * + * @author Michael Alexander Montoya (@cureprotocols) + * @see Reservoir Sampling - Wikipedia + */ +public final class ReservoirSampling { + + // Prevent instantiation of utility class + private ReservoirSampling() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * Selects k random elements from a stream using reservoir sampling. + * + * @param stream The input stream as an array of integers. + * @param sampleSize The number of elements to sample. + * @return A list containing k randomly selected elements. + */ + public static List sample(int[] stream, int sampleSize) { + if (sampleSize > stream.length) { + throw new IllegalArgumentException("Sample size cannot exceed stream size."); + } + + List reservoir = new ArrayList<>(sampleSize); + Random rand = new Random(); + + for (int i = 0; i < stream.length; i++) { + if (i < sampleSize) { + reservoir.add(stream[i]); + } else { + int j = rand.nextInt(i + 1); + if (j < sampleSize) { + reservoir.set(j, stream[i]); + } + } + } + + return reservoir; + } +} diff --git a/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java b/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java new file mode 100644 index 000000000000..e5f474085367 --- /dev/null +++ b/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java @@ -0,0 +1,21 @@ +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 ... +*/ + +public final class FibonacciSeries { + private FibonacciSeries() { + throw new UnsupportedOperationException("Utility class"); + } + public static int fibonacci(int n) { + if (n <= 1) { + return n; + } else { + return fibonacci(n - 1) + fibonacci(n - 2); + } + } +} diff --git a/src/main/java/com/thealgorithms/Recursion/GenerateSubsets.java b/src/main/java/com/thealgorithms/recursion/GenerateSubsets.java similarity index 96% rename from src/main/java/com/thealgorithms/Recursion/GenerateSubsets.java rename to src/main/java/com/thealgorithms/recursion/GenerateSubsets.java index 417bf1307790..5a3ff2e88040 100644 --- a/src/main/java/com/thealgorithms/Recursion/GenerateSubsets.java +++ b/src/main/java/com/thealgorithms/recursion/GenerateSubsets.java @@ -1,4 +1,4 @@ -package com.thealgorithms.Recursion; +package com.thealgorithms.recursion; // program to find power set of a string diff --git a/src/main/java/com/thealgorithms/scheduling/AgingScheduling.java b/src/main/java/com/thealgorithms/scheduling/AgingScheduling.java new file mode 100644 index 000000000000..1e5512be9edd --- /dev/null +++ b/src/main/java/com/thealgorithms/scheduling/AgingScheduling.java @@ -0,0 +1,62 @@ +package com.thealgorithms.scheduling; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * AgingScheduling is an algorithm designed to prevent starvation + * by gradually increasing the priority of waiting tasks. + * The longer a process waits, the higher its priority becomes. + * + * Use Case: Useful in systems with mixed workloads to avoid + * lower-priority tasks being starved by higher-priority tasks. + * + * @author Hardvan + */ +public final class AgingScheduling { + + static class Task { + String name; + int waitTime; + int priority; + + Task(String name, int priority) { + this.name = name; + this.priority = priority; + this.waitTime = 0; + } + } + + private final Queue taskQueue; + + public AgingScheduling() { + taskQueue = new LinkedList<>(); + } + + /** + * Adds a task to the scheduler with a given priority. + * + * @param name name of the task + * @param priority priority of the task + */ + public void addTask(String name, int priority) { + taskQueue.offer(new Task(name, priority)); + } + + /** + * Schedules the next task based on the priority and wait time. + * The priority of a task increases with the time it spends waiting. + * + * @return name of the next task to be executed + */ + public String scheduleNext() { + if (taskQueue.isEmpty()) { + return null; + } + Task nextTask = taskQueue.poll(); + nextTask.waitTime++; + nextTask.priority += nextTask.waitTime; + taskQueue.offer(nextTask); + return nextTask.name; + } +} diff --git a/src/main/java/com/thealgorithms/scheduling/EDFScheduling.java b/src/main/java/com/thealgorithms/scheduling/EDFScheduling.java new file mode 100644 index 000000000000..5ba79cdbb73a --- /dev/null +++ b/src/main/java/com/thealgorithms/scheduling/EDFScheduling.java @@ -0,0 +1,99 @@ +package com.thealgorithms.scheduling; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * The Earliest Deadline First (EDF) Scheduling class implements a dynamic scheduling algorithm. + * It assigns the CPU to processes with the earliest deadlines, ensuring that deadlines are met if possible. + * This scheduling algorithm is ideal for real-time systems where meeting deadlines is critical. + */ +public final class EDFScheduling { + private EDFScheduling() { + } + + private List processes; + + /** + * Constructs an EDFScheduling object with a list of processes. + * + * @param processes List of processes to be scheduled. + */ + public EDFScheduling(final List processes) { + this.processes = processes; + } + + /** + * Schedules the processes using Earliest Deadline First (EDF) scheduling. + * Processes are sorted by their deadlines, and the method simulates their execution. + * It calculates the waiting time and turnaround time for each process. + * + * @return List of processes after they have been executed in order of earliest deadline first. + */ + public List scheduleProcesses() { + processes.sort(Comparator.comparingInt(Process::getDeadline)); + + int currentTime = 0; + List executedProcesses = new ArrayList<>(); + + for (Process process : processes) { + process.setWaitingTime(currentTime); + currentTime += process.getBurstTime(); + process.setTurnAroundTime(process.getWaitingTime() + process.getBurstTime()); + + if (currentTime > process.getDeadline()) { + System.out.println("Warning: Process " + process.getProcessId() + " missed its deadline."); + } + + executedProcesses.add(process); + } + + return executedProcesses; + } + + /** + * The Process class represents a process with an ID, burst time, deadline, waiting time, and turnaround time. + */ + public static class Process { + private String processId; + private int burstTime; + private int deadline; + private int waitingTime; + private int turnAroundTime; + + public Process(String processId, int burstTime, int deadline) { + this.processId = processId; + this.burstTime = burstTime; + this.deadline = deadline; + } + + public String getProcessId() { + return processId; + } + + public int getBurstTime() { + return burstTime; + } + + public int getDeadline() { + return deadline; + } + + public int getWaitingTime() { + return waitingTime; + } + + public void setWaitingTime(int waitingTime) { + this.waitingTime = waitingTime; + } + + public int getTurnAroundTime() { + return turnAroundTime; + } + + public void setTurnAroundTime(int turnAroundTime) { + this.turnAroundTime = turnAroundTime; + } + } +} diff --git a/src/main/java/com/thealgorithms/scheduling/FairShareScheduling.java b/src/main/java/com/thealgorithms/scheduling/FairShareScheduling.java new file mode 100644 index 000000000000..776fc59c0c4d --- /dev/null +++ b/src/main/java/com/thealgorithms/scheduling/FairShareScheduling.java @@ -0,0 +1,65 @@ +package com.thealgorithms.scheduling; + +import java.util.HashMap; +import java.util.Map; + +/** + * FairShareScheduling allocates CPU resources equally among users or groups + * instead of individual tasks. Each group gets a proportional share, + * preventing resource hogging by a single user's processes. + * + * Use Case: Multi-user systems where users submit multiple tasks simultaneously, + * such as cloud environments. + * + * @author Hardvan + */ +public final class FairShareScheduling { + + static class User { + String name; + int allocatedResources; + int totalWeight; + + User(String name) { + this.name = name; + this.allocatedResources = 0; + this.totalWeight = 0; + } + + void addWeight(int weight) { + this.totalWeight += weight; + } + } + + private final Map users; // username -> User + + public FairShareScheduling() { + users = new HashMap<>(); + } + + public void addUser(String userName) { + users.putIfAbsent(userName, new User(userName)); + } + + public void addTask(String userName, int weight) { + User user = users.get(userName); + if (user != null) { + user.addWeight(weight); + } + } + + public void allocateResources(int totalResources) { + int totalWeights = users.values().stream().mapToInt(user -> user.totalWeight).sum(); + for (User user : users.values()) { + user.allocatedResources = (int) ((double) user.totalWeight / totalWeights * totalResources); + } + } + + public Map getAllocatedResources() { + Map allocation = new HashMap<>(); + for (User user : users.values()) { + allocation.put(user.name, user.allocatedResources); + } + return allocation; + } +} diff --git a/src/main/java/com/thealgorithms/scheduling/GangScheduling.java b/src/main/java/com/thealgorithms/scheduling/GangScheduling.java new file mode 100644 index 000000000000..ac1ce8ddd6ae --- /dev/null +++ b/src/main/java/com/thealgorithms/scheduling/GangScheduling.java @@ -0,0 +1,61 @@ +package com.thealgorithms.scheduling; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * GangScheduling groups related tasks (gangs) to run simultaneously on multiple processors. + * All tasks in a gang are executed together or not at all. + * + * Use Case: Parallel computing environments where multiple threads of a program + * need to run concurrently for optimal performance. + * + * @author Hardvan + */ +public final class GangScheduling { + + static class Gang { + String name; + List tasks; + + Gang(String name) { + this.name = name; + this.tasks = new ArrayList<>(); + } + + void addTask(String task) { + tasks.add(task); + } + + List getTasks() { + return tasks; + } + } + + private final Map gangs; + + public GangScheduling() { + gangs = new HashMap<>(); + } + + public void addGang(String gangName) { + gangs.putIfAbsent(gangName, new Gang(gangName)); + } + + public void addTaskToGang(String gangName, String task) { + Gang gang = gangs.get(gangName); + if (gang != null) { + gang.addTask(task); + } + } + + public Map> getGangSchedules() { + Map> schedules = new HashMap<>(); + for (Gang gang : gangs.values()) { + schedules.put(gang.name, gang.getTasks()); + } + return schedules; + } +} diff --git a/src/main/java/com/thealgorithms/scheduling/JobSchedulingWithDeadline.java b/src/main/java/com/thealgorithms/scheduling/JobSchedulingWithDeadline.java new file mode 100644 index 000000000000..49638d39fc2a --- /dev/null +++ b/src/main/java/com/thealgorithms/scheduling/JobSchedulingWithDeadline.java @@ -0,0 +1,88 @@ +package com.thealgorithms.scheduling; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * A class that implements a job scheduling algorithm to maximize profit + * while adhering to job deadlines and arrival times. + * + * This class provides functionality to schedule jobs based on their profit, + * arrival time, and deadlines to ensure that the maximum number of jobs is completed + * within the given timeframe. It sorts the jobs in decreasing order of profit + * and attempts to assign them to the latest possible time slots. + */ +public final class JobSchedulingWithDeadline { + private JobSchedulingWithDeadline() { + } + + /** + * Represents a job with an ID, arrival time, deadline, and profit. + * + * Each job has a unique identifier, an arrival time (when it becomes available for scheduling), + * a deadline by which it must be completed, and a profit associated with completing the job. + */ + static class Job { + int jobId; + int arrivalTime; + int deadline; + int profit; + + /** + * Constructs a Job instance with the specified job ID, arrival time, deadline, and profit. + * + * @param jobId Unique identifier for the job + * @param arrivalTime Time when the job becomes available for scheduling + * @param deadline Deadline for completing the job + * @param profit Profit earned upon completing the job + */ + Job(int jobId, int arrivalTime, int deadline, int profit) { + this.jobId = jobId; + this.arrivalTime = arrivalTime; + this.deadline = deadline; + this.profit = profit; + } + } + + /** + * Schedules jobs to maximize profit while respecting their deadlines and arrival times. + * + * This method sorts the jobs in descending order of profit and attempts + * to allocate them to time slots that are before or on their deadlines, + * provided they have arrived. The function returns an array where the first element + * is the total number of jobs scheduled and the second element is the total profit earned. + * + * @param jobs An array of Job objects, each representing a job with an ID, arrival time, + * deadline, and profit. + * @return An array of two integers: the first element is the count of jobs + * that were successfully scheduled, and the second element is the + * total profit earned from those jobs. + */ + public static int[] jobSequencingWithDeadlines(Job[] jobs) { + Arrays.sort(jobs, Comparator.comparingInt(job -> - job.profit)); + + int maxDeadline = Arrays.stream(jobs).mapToInt(job -> job.deadline).max().orElse(0); + + int[] timeSlots = new int[maxDeadline]; + Arrays.fill(timeSlots, -1); + + int count = 0; + int maxProfit = 0; + + // Schedule the jobs + for (Job job : jobs) { + if (job.arrivalTime <= job.deadline) { + for (int i = Math.min(job.deadline - 1, maxDeadline - 1); i >= job.arrivalTime - 1; i--) { + if (timeSlots[i] == -1) { + timeSlots[i] = job.jobId; + count++; + maxProfit += job.profit; + break; + } + } + } + } + + return new int[] {count, maxProfit}; + } +} diff --git a/src/main/java/com/thealgorithms/scheduling/LotteryScheduling.java b/src/main/java/com/thealgorithms/scheduling/LotteryScheduling.java new file mode 100644 index 000000000000..cea0c793d340 --- /dev/null +++ b/src/main/java/com/thealgorithms/scheduling/LotteryScheduling.java @@ -0,0 +1,141 @@ +package com.thealgorithms.scheduling; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * The LotteryScheduling class implements the Lottery Scheduling algorithm, which is + * a probabilistic CPU scheduling algorithm. Processes are assigned tickets, and + * the CPU is allocated to a randomly selected process based on ticket count. + * Processes with more tickets have a higher chance of being selected. + */ +public final class LotteryScheduling { + private LotteryScheduling() { + } + + private List processes; + private Random random; + + /** + * Constructs a LotteryScheduling object with the provided list of processes. + * + * @param processes List of processes to be scheduled using Lottery Scheduling. + */ + public LotteryScheduling(final List processes) { + this.processes = processes; + this.random = new Random(); + } + + /** + * Constructs a LotteryScheduling object with the provided list of processes and a Random object. + * + * @param processes List of processes to be scheduled using Lottery Scheduling. + * @param random Random object used for generating random numbers. + */ + public LotteryScheduling(final List processes, Random random) { + this.processes = processes; + this.random = random; + } + + /** + * Schedules the processes using the Lottery Scheduling algorithm. + * Each process is assigned a certain number of tickets, and the algorithm randomly + * selects a process to execute based on ticket count. The method calculates the + * waiting time and turnaround time for each process and simulates their execution. + */ + public List scheduleProcesses() { + int totalTickets = processes.stream().mapToInt(Process::getTickets).sum(); + int currentTime = 0; + List executedProcesses = new ArrayList<>(); + + while (!processes.isEmpty()) { + int winningTicket = random.nextInt(totalTickets) + 1; + Process selectedProcess = selectProcessByTicket(winningTicket); + + if (selectedProcess == null) { + // This should not happen in normal circumstances, but we'll handle it just in case + System.err.println("Error: No process selected. Recalculating total tickets."); + totalTickets = processes.stream().mapToInt(Process::getTickets).sum(); + continue; + } + + selectedProcess.setWaitingTime(currentTime); + currentTime += selectedProcess.getBurstTime(); + selectedProcess.setTurnAroundTime(selectedProcess.getWaitingTime() + selectedProcess.getBurstTime()); + + executedProcesses.add(selectedProcess); + processes.remove(selectedProcess); + + totalTickets = processes.stream().mapToInt(Process::getTickets).sum(); + } + + return executedProcesses; + } + + /** + * Selects a process based on a winning ticket. The method iterates over the + * list of processes, and as the ticket sum accumulates, it checks if the + * current process holds the winning ticket. + * + * @param winningTicket The randomly generated ticket number that determines the selected process. + * @return The process associated with the winning ticket. + */ + private Process selectProcessByTicket(int winningTicket) { + int ticketSum = 0; + for (Process process : processes) { + ticketSum += process.getTickets(); + if (ticketSum >= winningTicket) { + return process; + } + } + return null; + } + + /** + * The Process class represents a process in the scheduling system. Each process has + * an ID, burst time (CPU time required for execution), number of tickets (used in + * lottery selection), waiting time, and turnaround time. + */ + public static class Process { + private String processId; + private int burstTime; + private int tickets; + private int waitingTime; + private int turnAroundTime; + + public Process(String processId, int burstTime, int tickets) { + this.processId = processId; + this.burstTime = burstTime; + this.tickets = tickets; + } + + public String getProcessId() { + return processId; + } + + public int getBurstTime() { + return burstTime; + } + + public int getTickets() { + return tickets; + } + + public int getWaitingTime() { + return waitingTime; + } + + public void setWaitingTime(int waitingTime) { + this.waitingTime = waitingTime; + } + + public int getTurnAroundTime() { + return turnAroundTime; + } + + public void setTurnAroundTime(int turnAroundTime) { + this.turnAroundTime = turnAroundTime; + } + } +} diff --git a/src/main/java/com/thealgorithms/scheduling/MultiAgentScheduling.java b/src/main/java/com/thealgorithms/scheduling/MultiAgentScheduling.java new file mode 100644 index 000000000000..113b1691dec1 --- /dev/null +++ b/src/main/java/com/thealgorithms/scheduling/MultiAgentScheduling.java @@ -0,0 +1,72 @@ +package com.thealgorithms.scheduling; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * MultiAgentScheduling assigns tasks to different autonomous agents + * who independently decide the execution order of their assigned tasks. + * The focus is on collaboration between agents to optimize the overall schedule. + * + * Use Case: Distributed scheduling in decentralized systems like IoT networks. + * + * @author Hardvan + */ +public class MultiAgentScheduling { + + static class Agent { + String name; + List tasks; + + Agent(String name) { + this.name = name; + this.tasks = new ArrayList<>(); + } + + void addTask(String task) { + tasks.add(task); + } + + List getTasks() { + return tasks; + } + } + + private final Map agents; + + public MultiAgentScheduling() { + agents = new HashMap<>(); + } + + public void addAgent(String agentName) { + agents.putIfAbsent(agentName, new Agent(agentName)); + } + + /** + * Assign a task to a specific agent. + * + * @param agentName the name of the agent + * @param task the task to be assigned + */ + public void assignTask(String agentName, String task) { + Agent agent = agents.get(agentName); + if (agent != null) { + agent.addTask(task); + } + } + + /** + * Get the scheduled tasks for each agent. + * + * @return a map of agent names to their scheduled tasks + */ + public Map> getScheduledTasks() { + Map> schedule = new HashMap<>(); + for (Agent agent : agents.values()) { + schedule.put(agent.name, agent.getTasks()); + } + return schedule; + } +} diff --git a/src/main/java/com/thealgorithms/scheduling/NonPreemptivePriorityScheduling.java b/src/main/java/com/thealgorithms/scheduling/NonPreemptivePriorityScheduling.java new file mode 100644 index 000000000000..414de4b24e36 --- /dev/null +++ b/src/main/java/com/thealgorithms/scheduling/NonPreemptivePriorityScheduling.java @@ -0,0 +1,133 @@ +package com.thealgorithms.scheduling; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.PriorityQueue; +import java.util.Queue; + +/** + * This class implements the Non-Preemptive Priority Scheduling algorithm. + * Processes are executed in order of their priority. The process with the + * highest priority (lower priority number) is executed first, + * and once a process starts executing, it cannot be preempted. + */ +public final class NonPreemptivePriorityScheduling { + + private NonPreemptivePriorityScheduling() { + } + + /** + * Represents a process with an ID, burst time, priority, arrival time, and start time. + */ + static class Process implements Comparable { + int id; + int arrivalTime; + int startTime; + int burstTime; + int priority; + + /** + * Constructs a Process instance with the specified parameters. + * + * @param id Unique identifier for the process + * @param arrivalTime Time when the process arrives in the system + * @param burstTime Time required for the process execution + * @param priority Priority of the process + */ + Process(int id, int arrivalTime, int burstTime, int priority) { + this.id = id; + this.arrivalTime = arrivalTime; + this.startTime = -1; + this.burstTime = burstTime; + this.priority = priority; + } + + /** + * Compare based on priority for scheduling. The process with the lowest + * priority is selected first. + * If two processes have the same priority, the one that arrives earlier is selected. + * + * @param other The other process to compare against + * @return A negative integer, zero, or a positive integer as this process + * is less than, equal to, or greater than the specified process. + */ + @Override + public int compareTo(Process other) { + if (this.priority == other.priority) { + return Integer.compare(this.arrivalTime, other.arrivalTime); + } + return Integer.compare(this.priority, other.priority); + } + } + + /** + * Schedules processes based on their priority in a non-preemptive manner, considering their arrival times. + * + * @param processes Array of processes to be scheduled. + * @return Array of processes in the order they are executed. + */ + public static Process[] scheduleProcesses(Process[] processes) { + PriorityQueue pq = new PriorityQueue<>(); + Queue waitingQueue = new LinkedList<>(); + int currentTime = 0; + int index = 0; + Process[] executionOrder = new Process[processes.length]; + + Collections.addAll(waitingQueue, processes); + + while (!waitingQueue.isEmpty() || !pq.isEmpty()) { + // Add processes that have arrived to the priority queue + while (!waitingQueue.isEmpty() && waitingQueue.peek().arrivalTime <= currentTime) { + pq.add(waitingQueue.poll()); + } + + if (!pq.isEmpty()) { + Process currentProcess = pq.poll(); + currentProcess.startTime = currentTime; + executionOrder[index++] = currentProcess; + currentTime += currentProcess.burstTime; + } else { + // If no process is ready, move to the next arrival time + currentTime = waitingQueue.peek().arrivalTime; + } + } + + return executionOrder; + } + + /** + * Calculates the average waiting time of the processes. + * + * @param processes Array of processes. + * @param executionOrder Array of processes in execution order. + * @return Average waiting time. + */ + public static double calculateAverageWaitingTime(Process[] processes, Process[] executionOrder) { + int totalWaitingTime = 0; + + for (Process process : executionOrder) { + int waitingTime = process.startTime - process.arrivalTime; + totalWaitingTime += waitingTime; + } + + return (double) totalWaitingTime / processes.length; + } + + /** + * Calculates the average turn-around time of the processes. + * + * @param processes Array of processes. + * @param executionOrder Array of processes in execution order. + * @return Average turn-around time. + */ + public static double calculateAverageTurnaroundTime(Process[] processes, Process[] executionOrder) { + int totalTurnaroundTime = 0; + + for (Process process : executionOrder) { + int turnaroundTime = process.startTime + process.burstTime - process.arrivalTime; + totalTurnaroundTime += turnaroundTime; + } + + return (double) totalTurnaroundTime / processes.length; + } +} diff --git a/src/main/java/com/thealgorithms/scheduling/PreemptivePriorityScheduling.java b/src/main/java/com/thealgorithms/scheduling/PreemptivePriorityScheduling.java index 27d85a94d6f5..66c99661d13d 100644 --- a/src/main/java/com/thealgorithms/scheduling/PreemptivePriorityScheduling.java +++ b/src/main/java/com/thealgorithms/scheduling/PreemptivePriorityScheduling.java @@ -2,6 +2,7 @@ import com.thealgorithms.devutils.entities.ProcessDetails; import java.util.ArrayList; +import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.PriorityQueue; @@ -15,7 +16,7 @@ public class PreemptivePriorityScheduling { protected final List processes; protected final List ganttChart; - public PreemptivePriorityScheduling(List processes) { + public PreemptivePriorityScheduling(Collection processes) { this.processes = new ArrayList<>(processes); this.ganttChart = new ArrayList<>(); } diff --git a/src/main/java/com/thealgorithms/scheduling/ProportionalFairScheduling.java b/src/main/java/com/thealgorithms/scheduling/ProportionalFairScheduling.java new file mode 100644 index 000000000000..fd78dc571819 --- /dev/null +++ b/src/main/java/com/thealgorithms/scheduling/ProportionalFairScheduling.java @@ -0,0 +1,54 @@ +package com.thealgorithms.scheduling; + +import java.util.ArrayList; +import java.util.List; + +/** + * ProportionalFairScheduling allocates resources to processes based on their + * proportional weight or importance. It aims to balance fairness with + * priority, ensuring that higher-weight processes receive a larger share of resources. + * + * Use Case: Network bandwidth allocation in cellular networks (4G/5G), + * where devices receive a proportional share of bandwidth. + * + * @author Hardvan + */ +public final class ProportionalFairScheduling { + + static class Process { + String name; + int weight; + int allocatedResources; + + Process(String name, int weight) { + this.name = name; + this.weight = weight; + this.allocatedResources = 0; + } + } + + private final List processes; + + public ProportionalFairScheduling() { + processes = new ArrayList<>(); + } + + public void addProcess(String name, int weight) { + processes.add(new Process(name, weight)); + } + + public void allocateResources(int totalResources) { + int totalWeight = processes.stream().mapToInt(p -> p.weight).sum(); + for (Process process : processes) { + process.allocatedResources = (int) ((double) process.weight / totalWeight * totalResources); + } + } + + public List getAllocatedResources() { + List allocation = new ArrayList<>(); + for (Process process : processes) { + allocation.add(process.name + ": " + process.allocatedResources); + } + return allocation; + } +} diff --git a/src/main/java/com/thealgorithms/scheduling/RandomScheduling.java b/src/main/java/com/thealgorithms/scheduling/RandomScheduling.java new file mode 100644 index 000000000000..b7e863b5cfd8 --- /dev/null +++ b/src/main/java/com/thealgorithms/scheduling/RandomScheduling.java @@ -0,0 +1,45 @@ +package com.thealgorithms.scheduling; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +/** + * RandomScheduling is an algorithm that assigns tasks in a random order. + * It doesn't consider priority, deadlines, or burst times, making it + * inefficient but useful in scenarios where fairness or unpredictability + * is required (e.g., load balancing in distributed systems). + * + * Use Case: Distributed systems where randomness helps avoid task starvation. + * + * @author Hardvan + */ +public final class RandomScheduling { + + private final List tasks; + private final Random random; + + /** + * Constructs a new RandomScheduling instance. + * + * @param tasks A collection of task names to be scheduled. + * @param random A Random instance for generating random numbers. + */ + public RandomScheduling(Collection tasks, Random random) { + this.tasks = new ArrayList<>(tasks); + this.random = random; + } + + /** + * Schedules the tasks randomly and returns the randomized order. + * + * @return A list representing the tasks in their randomized execution order. + */ + public List schedule() { + List shuffledTasks = new ArrayList<>(tasks); + Collections.shuffle(shuffledTasks, random); + return shuffledTasks; + } +} diff --git a/src/main/java/com/thealgorithms/scheduling/SJFScheduling.java b/src/main/java/com/thealgorithms/scheduling/SJFScheduling.java index ca2144e4924f..cbbc65a3afc5 100644 --- a/src/main/java/com/thealgorithms/scheduling/SJFScheduling.java +++ b/src/main/java/com/thealgorithms/scheduling/SJFScheduling.java @@ -2,6 +2,7 @@ import com.thealgorithms.devutils.entities.ProcessDetails; import java.util.ArrayList; +import java.util.List; /** * Implementation of Shortest Job First Algorithm: The algorithm allows the waiting process with the @@ -13,6 +14,18 @@ public class SJFScheduling { protected ArrayList processes; protected ArrayList schedule; + private static void sortProcessesByArrivalTime(List processes) { + for (int i = 0; i < processes.size(); i++) { + for (int j = i + 1; j < processes.size() - 1; j++) { + if (processes.get(j).getArrivalTime() > processes.get(j + 1).getArrivalTime()) { + final var temp = processes.get(j); + processes.set(j, processes.get(j + 1)); + processes.set(j + 1, temp); + } + } + } + } + /** * a simple constructor * @param processes a list of processes the user wants to schedule @@ -21,22 +34,10 @@ public class SJFScheduling { SJFScheduling(final ArrayList processes) { this.processes = processes; schedule = new ArrayList<>(); - sortByArrivalTime(); + sortProcessesByArrivalTime(this.processes); } protected void sortByArrivalTime() { - int size = processes.size(); - int i; - int j; - ProcessDetails temp; - for (i = 0; i < size; i++) { - for (j = i + 1; j < size - 1; j++) { - if (processes.get(j).getArrivalTime() > processes.get(j + 1).getArrivalTime()) { - temp = processes.get(j); - processes.set(j, processes.get(j + 1)); - processes.set(j + 1, temp); - } - } - } + sortProcessesByArrivalTime(processes); } /** @@ -87,7 +88,7 @@ public void scheduleProcesses() { * @return returns the process' with the shortest burst time OR NULL if there are no ready * processes */ - private ProcessDetails findShortestJob(ArrayList readyProcesses) { + private ProcessDetails findShortestJob(List readyProcesses) { if (readyProcesses.isEmpty()) { return null; } diff --git a/src/main/java/com/thealgorithms/scheduling/SelfAdjustingScheduling.java b/src/main/java/com/thealgorithms/scheduling/SelfAdjustingScheduling.java new file mode 100644 index 000000000000..15159a07d2d4 --- /dev/null +++ b/src/main/java/com/thealgorithms/scheduling/SelfAdjustingScheduling.java @@ -0,0 +1,63 @@ +package com.thealgorithms.scheduling; + +import java.util.PriorityQueue; + +/** + * SelfAdjustingScheduling is an algorithm where tasks dynamically adjust + * their priority based on real-time feedback, such as wait time and CPU usage. + * Tasks that wait longer will automatically increase their priority, + * allowing for better responsiveness and fairness in task handling. + * + * Use Case: Real-time systems that require dynamic prioritization + * of tasks to maintain system responsiveness and fairness. + * + * @author Hardvan + */ +public final class SelfAdjustingScheduling { + + private static class Task implements Comparable { + String name; + int waitTime; + int priority; + + Task(String name, int priority) { + this.name = name; + this.waitTime = 0; + this.priority = priority; + } + + void incrementWaitTime() { + waitTime++; + priority = priority + waitTime; + } + + @Override + public int compareTo(Task other) { + return Integer.compare(this.priority, other.priority); + } + } + + private final PriorityQueue taskQueue; + + public SelfAdjustingScheduling() { + taskQueue = new PriorityQueue<>(); + } + + public void addTask(String name, int priority) { + taskQueue.offer(new Task(name, priority)); + } + + public String scheduleNext() { + if (taskQueue.isEmpty()) { + return null; + } + Task nextTask = taskQueue.poll(); + nextTask.incrementWaitTime(); + taskQueue.offer(nextTask); + return nextTask.name; + } + + public boolean isEmpty() { + return taskQueue.isEmpty(); + } +} diff --git a/src/main/java/com/thealgorithms/scheduling/SlackTimeScheduling.java b/src/main/java/com/thealgorithms/scheduling/SlackTimeScheduling.java new file mode 100644 index 000000000000..bbfd36f0f660 --- /dev/null +++ b/src/main/java/com/thealgorithms/scheduling/SlackTimeScheduling.java @@ -0,0 +1,64 @@ +package com.thealgorithms.scheduling; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * SlackTimeScheduling is an algorithm that prioritizes tasks based on their + * slack time, which is defined as the difference between the task's deadline + * and the time required to execute it. Tasks with less slack time are prioritized. + * + * Use Case: Real-time systems with hard deadlines, such as robotics or embedded systems. + * + * @author Hardvan + */ +public class SlackTimeScheduling { + + static class Task { + String name; + int executionTime; + int deadline; + + Task(String name, int executionTime, int deadline) { + this.name = name; + this.executionTime = executionTime; + this.deadline = deadline; + } + + int getSlackTime() { + return deadline - executionTime; + } + } + + private final List tasks; + + public SlackTimeScheduling() { + tasks = new ArrayList<>(); + } + + /** + * Adds a task to the scheduler. + * + * @param name the name of the task + * @param executionTime the time required to execute the task + * @param deadline the deadline by which the task must be completed + */ + public void addTask(String name, int executionTime, int deadline) { + tasks.add(new Task(name, executionTime, deadline)); + } + + /** + * Schedules the tasks based on their slack time. + * + * @return the order in which the tasks should be executed + */ + public List scheduleTasks() { + tasks.sort(Comparator.comparingInt(Task::getSlackTime)); + List scheduledOrder = new ArrayList<>(); + for (Task task : tasks) { + scheduledOrder.add(task.name); + } + return scheduledOrder; + } +} diff --git a/src/main/java/com/thealgorithms/scheduling/diskscheduling/CircularLookScheduling.java b/src/main/java/com/thealgorithms/scheduling/diskscheduling/CircularLookScheduling.java new file mode 100644 index 000000000000..2230ecaf35a6 --- /dev/null +++ b/src/main/java/com/thealgorithms/scheduling/diskscheduling/CircularLookScheduling.java @@ -0,0 +1,80 @@ +package com.thealgorithms.scheduling.diskscheduling; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Circular Look Scheduling (C-LOOK) is a disk scheduling algorithm similar to + * the C-SCAN algorithm but with a key difference. In C-LOOK, the disk arm also + * moves in one direction to service requests, but instead of going all the way + * to the end of the disk, it only goes as far as the furthest request in the + * current direction. After servicing the last request in the current direction, + * the arm immediately jumps back to the closest request on the other side without + * moving to the disk's extreme ends. This reduces the unnecessary movement of the + * disk arm, resulting in better performance compared to C-SCAN, while still + * maintaining fair wait times for requests. + */ +public class CircularLookScheduling { + private int currentPosition; + private boolean movingUp; + private final int maxCylinder; + + public CircularLookScheduling(int startPosition, boolean movingUp, int maxCylinder) { + this.currentPosition = startPosition; + this.movingUp = movingUp; + this.maxCylinder = maxCylinder; + } + + public List execute(List requests) { + List result = new ArrayList<>(); + + // Filter and sort valid requests in both directions + List upRequests = new ArrayList<>(); + List downRequests = new ArrayList<>(); + + for (int request : requests) { + if (request >= 0 && request < maxCylinder) { + if (request > currentPosition) { + upRequests.add(request); + } else if (request < currentPosition) { + downRequests.add(request); + } + } + } + + Collections.sort(upRequests); + Collections.sort(downRequests); + + if (movingUp) { + // Process all requests in the upward direction + result.addAll(upRequests); + + // Jump to the lowest request and process all requests in the downward direction + result.addAll(downRequests); + } else { + // Process all requests in the downward direction (in reverse order) + Collections.reverse(downRequests); + result.addAll(downRequests); + + // Jump to the highest request and process all requests in the upward direction (in reverse order) + Collections.reverse(upRequests); + result.addAll(upRequests); + } + + // Update current position to the last processed request + if (!result.isEmpty()) { + currentPosition = result.get(result.size() - 1); + } + + return result; + } + + public int getCurrentPosition() { + return currentPosition; + } + + public boolean isMovingUp() { + return movingUp; + } +} diff --git a/src/main/java/com/thealgorithms/scheduling/diskscheduling/CircularScanScheduling.java b/src/main/java/com/thealgorithms/scheduling/diskscheduling/CircularScanScheduling.java new file mode 100644 index 000000000000..a31c3d99a89e --- /dev/null +++ b/src/main/java/com/thealgorithms/scheduling/diskscheduling/CircularScanScheduling.java @@ -0,0 +1,83 @@ +package com.thealgorithms.scheduling.diskscheduling; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Circular Scan Scheduling (C-SCAN) is a disk scheduling algorithm that + * works by moving the disk arm in one direction to service requests until + * it reaches the end of the disk. Once it reaches the end, instead of reversing + * direction like in the SCAN algorithm, the arm moves back to the starting point + * without servicing any requests. This ensures a more uniform wait time for all + * requests, especially those near the disk edges. The algorithm then continues in + * the same direction, making it effective for balancing service time across all disk sectors. + */ +public class CircularScanScheduling { + private int currentPosition; + private boolean movingUp; + private final int diskSize; + + public CircularScanScheduling(int startPosition, boolean movingUp, int diskSize) { + this.currentPosition = startPosition; + this.movingUp = movingUp; + this.diskSize = diskSize; + } + + public List execute(List requests) { + if (requests.isEmpty()) { + return new ArrayList<>(); // Return empty list if there are no requests + } + + List sortedRequests = new ArrayList<>(requests); + Collections.sort(sortedRequests); + + List result = new ArrayList<>(); + + if (movingUp) { + // Moving up: process requests >= current position + for (int request : sortedRequests) { + if (request >= currentPosition && request < diskSize) { + result.add(request); + } + } + + // Jump to the smallest request and continue processing from the start + for (int request : sortedRequests) { + if (request < currentPosition) { + result.add(request); + } + } + } else { + // Moving down: process requests <= current position in reverse order + for (int i = sortedRequests.size() - 1; i >= 0; i--) { + int request = sortedRequests.get(i); + if (request <= currentPosition) { + result.add(request); + } + } + + // Jump to the largest request and continue processing in reverse order + for (int i = sortedRequests.size() - 1; i >= 0; i--) { + int request = sortedRequests.get(i); + if (request > currentPosition) { + result.add(request); + } + } + } + + // Set final position to the last request processed + if (!result.isEmpty()) { + currentPosition = result.get(result.size() - 1); + } + return result; + } + + public int getCurrentPosition() { + return currentPosition; + } + + public boolean isMovingUp() { + return movingUp; + } +} diff --git a/src/main/java/com/thealgorithms/scheduling/diskscheduling/LookScheduling.java b/src/main/java/com/thealgorithms/scheduling/diskscheduling/LookScheduling.java new file mode 100644 index 000000000000..beba69b05eca --- /dev/null +++ b/src/main/java/com/thealgorithms/scheduling/diskscheduling/LookScheduling.java @@ -0,0 +1,95 @@ +package com.thealgorithms.scheduling.diskscheduling; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * https://en.wikipedia.org/wiki/LOOK_algorithm + * Look Scheduling algorithm implementation. + * The Look algorithm moves the disk arm to the closest request in the current direction, + * and once it processes all requests in that direction, it reverses the direction. + */ +public class LookScheduling { + private final int maxTrack; + private final int currentPosition; + private boolean movingUp; + private int farthestPosition; + public LookScheduling(int startPosition, boolean initialDirection, int maxTrack) { + this.currentPosition = startPosition; + this.movingUp = initialDirection; + this.maxTrack = maxTrack; + } + + /** + * Executes the Look Scheduling algorithm on the given list of requests. + * + * @param requests List of disk requests. + * @return Order in which requests are processed. + */ + public List execute(List requests) { + List result = new ArrayList<>(); + List lower = new ArrayList<>(); + List upper = new ArrayList<>(); + + // Split requests into two lists based on their position relative to current position + for (int request : requests) { + if (request >= 0 && request < maxTrack) { + if (request < currentPosition) { + lower.add(request); + } else { + upper.add(request); + } + } + } + + // Sort the requests + Collections.sort(lower); + Collections.sort(upper); + + // Process the requests depending on the initial moving direction + if (movingUp) { + // Process requests in the upward direction + result.addAll(upper); + if (!upper.isEmpty()) { + farthestPosition = upper.get(upper.size() - 1); + } + + // Reverse the direction and process downward + movingUp = false; + Collections.reverse(lower); + result.addAll(lower); + if (!lower.isEmpty()) { + farthestPosition = Math.max(farthestPosition, lower.get(0)); + } + } else { + // Process requests in the downward direction + Collections.reverse(lower); + result.addAll(lower); + if (!lower.isEmpty()) { + farthestPosition = lower.get(0); + } + + // Reverse the direction and process upward + movingUp = true; + result.addAll(upper); + if (!upper.isEmpty()) { + farthestPosition = Math.max(farthestPosition, upper.get(upper.size() - 1)); + } + } + + return result; + } + + public int getCurrentPosition() { + return currentPosition; + } + + public boolean isMovingUp() { + return movingUp; + } + + public int getFarthestPosition() { + return farthestPosition; + } +} diff --git a/src/main/java/com/thealgorithms/scheduling/diskscheduling/SSFScheduling.java b/src/main/java/com/thealgorithms/scheduling/diskscheduling/SSFScheduling.java new file mode 100644 index 000000000000..261c1a388393 --- /dev/null +++ b/src/main/java/com/thealgorithms/scheduling/diskscheduling/SSFScheduling.java @@ -0,0 +1,57 @@ +package com.thealgorithms.scheduling.diskscheduling; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + *https://en.wikipedia.org/wiki/Shortest_seek_first + * Shortest Seek First (SFF) Scheduling algorithm implementation. + * The SFF algorithm selects the next request to be serviced based on the shortest distance + * from the current position of the disk arm. It continuously evaluates all pending requests + * and chooses the one that requires the least amount of movement to service. + * + * This approach minimizes the average seek time, making it efficient in terms of response + * time for individual requests. However, it may lead to starvation for requests located + * further away from the current position of the disk arm. + * + * The SFF algorithm is particularly effective in systems where quick response time + * is crucial, as it ensures that the most accessible requests are prioritized for servicing. + */ +public class SSFScheduling { + private int currentPosition; + + public SSFScheduling(int currentPosition) { + this.currentPosition = currentPosition; + } + + public List execute(Collection requests) { + List result = new ArrayList<>(requests); + List orderedRequests = new ArrayList<>(); + + while (!result.isEmpty()) { + int closest = findClosest(result); + orderedRequests.add(closest); + result.remove(Integer.valueOf(closest)); + currentPosition = closest; + } + return orderedRequests; + } + + private int findClosest(List requests) { + int minDistance = Integer.MAX_VALUE; + int closest = -1; + for (int request : requests) { + int distance = Math.abs(currentPosition - request); + if (distance < minDistance) { + minDistance = distance; + closest = request; + } + } + return closest; + } + + public int getCurrentPosition() { + return currentPosition; + } +} diff --git a/src/main/java/com/thealgorithms/scheduling/diskscheduling/ScanScheduling.java b/src/main/java/com/thealgorithms/scheduling/diskscheduling/ScanScheduling.java new file mode 100644 index 000000000000..2c4fa7844a12 --- /dev/null +++ b/src/main/java/com/thealgorithms/scheduling/diskscheduling/ScanScheduling.java @@ -0,0 +1,82 @@ +package com.thealgorithms.scheduling.diskscheduling; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * https://en.wikipedia.org/wiki/Elevator_algorithm + * SCAN Scheduling algorithm implementation. + * The SCAN algorithm moves the disk arm towards one end of the disk, servicing all requests + * along the way until it reaches the end. Once it reaches the end, it reverses direction + * and services the requests on its way back. + * + * This algorithm ensures that all requests are serviced in a fair manner, + * while minimizing the seek time for requests located close to the current position + * of the disk arm. + * + * The SCAN algorithm is particularly useful in environments with a large number of + * disk requests, as it reduces the overall movement of the disk arm compared to + */ +public class ScanScheduling { + private int headPosition; + private int diskSize; + private boolean movingUp; + + public ScanScheduling(int headPosition, boolean movingUp, int diskSize) { + this.headPosition = headPosition; + this.movingUp = movingUp; + this.diskSize = diskSize; + } + + public List execute(List requests) { + // If the request list is empty, return an empty result + if (requests.isEmpty()) { + return new ArrayList<>(); + } + + List result = new ArrayList<>(); + List left = new ArrayList<>(); + List right = new ArrayList<>(); + + // Separate requests into those smaller than the current head position and those larger + for (int request : requests) { + if (request < headPosition) { + left.add(request); + } else { + right.add(request); + } + } + + // Sort the requests + Collections.sort(left); + Collections.sort(right); + + // Simulate the disk head movement + if (movingUp) { + // Head moving upward, process right-side requests first + result.addAll(right); + // After reaching the end of the disk, reverse direction and process left-side requests + result.add(diskSize - 1); // Simulate the head reaching the end of the disk + Collections.reverse(left); + result.addAll(left); + } else { + // Head moving downward, process left-side requests first + Collections.reverse(left); + result.addAll(left); + // After reaching the start of the disk, reverse direction and process right-side requests + result.add(0); // Simulate the head reaching the start of the disk + result.addAll(right); + } + + return result; + } + + public int getHeadPosition() { + return headPosition; + } + + public boolean isMovingUp() { + return movingUp; + } +} diff --git a/src/main/java/com/thealgorithms/searches/BM25InvertedIndex.java b/src/main/java/com/thealgorithms/searches/BM25InvertedIndex.java new file mode 100644 index 000000000000..aeddc591b32b --- /dev/null +++ b/src/main/java/com/thealgorithms/searches/BM25InvertedIndex.java @@ -0,0 +1,220 @@ +package com.thealgorithms.searches; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Inverted Index implementation with BM25 Scoring for movie search. + * This class supports adding movie documents and searching for terms + * within those documents using the BM25 algorithm. + * @author Prayas Kumar (https://github.com/prayas7102) + */ + +class Movie { + int docId; // Unique identifier for the movie + String name; // Movie name + double imdbRating; // IMDb rating of the movie + int releaseYear; // Year the movie was released + String content; // Full text content (could be the description or script) + + /** + * Constructor for the Movie class. + * @param docId Unique identifier for the movie. + * @param name Name of the movie. + * @param imdbRating IMDb rating of the movie. + * @param releaseYear Release year of the movie. + * @param content Content or description of the movie. + */ + Movie(int docId, String name, double imdbRating, int releaseYear, String content) { + this.docId = docId; + this.name = name; + this.imdbRating = imdbRating; + this.releaseYear = releaseYear; + this.content = content; + } + + /** + * Get all the words from the movie's name and content. + * Converts the name and content to lowercase and splits on non-word characters. + * @return Array of words from the movie name and content. + */ + public String[] getWords() { + return (name + " " + content).toLowerCase().split("\\W+"); + } + + @Override + public String toString() { + return "Movie{" + + "docId=" + docId + ", name='" + name + '\'' + ", imdbRating=" + imdbRating + ", releaseYear=" + releaseYear + '}'; + } +} + +class SearchResult { + int docId; // Unique identifier of the movie document + double relevanceScore; // Relevance score based on the BM25 algorithm + + /** + * Constructor for SearchResult class. + * @param docId Document ID (movie) for this search result. + * @param relevanceScore The relevance score based on BM25 scoring. + */ + SearchResult(int docId, double relevanceScore) { + this.docId = docId; + this.relevanceScore = relevanceScore; + } + + public int getDocId() { + return docId; + } + + @Override + public String toString() { + return "SearchResult{" + + "docId=" + docId + ", relevanceScore=" + relevanceScore + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SearchResult that = (SearchResult) o; + return docId == that.docId && Double.compare(that.relevanceScore, relevanceScore) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(docId, relevanceScore); + } + + public double getRelevanceScore() { + return this.relevanceScore; + } +} + +public final class BM25InvertedIndex { + private Map> index; // Inverted index mapping terms to document id and frequency + private Map movies; // Mapping of movie document IDs to Movie objects + private int totalDocuments; // Total number of movies/documents + private double avgDocumentLength; // Average length of documents (number of words) + private static final double K = 1.5; // BM25 tuning parameter, controls term frequency saturation + private static final double B = 0.75; // BM25 tuning parameter, controls length normalization + + /** + * Constructor for BM25InvertedIndex. + * Initializes the inverted index and movie storage. + */ + BM25InvertedIndex() { + index = new HashMap<>(); + movies = new HashMap<>(); + totalDocuments = 0; + avgDocumentLength = 0.0; + } + + /** + * Add a movie to the index. + * @param docId Unique identifier for the movie. + * @param name Name of the movie. + * @param imdbRating IMDb rating of the movie. + * @param releaseYear Release year of the movie. + * @param content Content or description of the movie. + */ + public void addMovie(int docId, String name, double imdbRating, int releaseYear, String content) { + Movie movie = new Movie(docId, name, imdbRating, releaseYear, content); + movies.put(docId, movie); + totalDocuments++; + + // Get words (terms) from the movie's name and content + String[] terms = movie.getWords(); + int docLength = terms.length; + + // Update the average document length + avgDocumentLength = (avgDocumentLength * (totalDocuments - 1) + docLength) / totalDocuments; + + // Update the inverted index + for (String term : terms) { + // Create a new entry if the term is not yet in the index + index.putIfAbsent(term, new HashMap<>()); + + // Get the list of documents containing the term + Map docList = index.get(term); + if (docList == null) { + docList = new HashMap<>(); + index.put(term, docList); // Ensure docList is added to the index + } + // Increment the term frequency in this document + docList.put(docId, docList.getOrDefault(docId, 0) + 1); + } + } + + public int getMoviesLength() { + return movies.size(); + } + + /** + * Search for documents containing a term using BM25 scoring. + * @param term The search term. + * @return A list of search results sorted by relevance score. + */ + public List search(String term) { + term = term.toLowerCase(); // Normalize search term + if (!index.containsKey(term)) { + return new ArrayList<>(); // Return empty list if term not found + } + + Map termDocs = index.get(term); // Documents containing the term + List results = new ArrayList<>(); + + // Compute IDF for the search term + double idf = computeIDF(termDocs.size()); + + // Calculate relevance scores for all documents containing the term + for (Map.Entry entry : termDocs.entrySet()) { + int docId = entry.getKey(); + int termFrequency = entry.getValue(); + Movie movie = movies.get(docId); + if (movie == null) { + continue; // Skip this document if movie doesn't exist + } + double docLength = movie.getWords().length; + + // Compute BM25 relevance score + double score = computeBM25Score(termFrequency, docLength, idf); + results.add(new SearchResult(docId, score)); + } + + // Sort the results by relevance score in descending order + results.sort((r1, r2) -> Double.compare(r2.relevanceScore, r1.relevanceScore)); + return results; + } + + /** + * Compute the BM25 score for a given term and document. + * @param termFrequency The frequency of the term in the document. + * @param docLength The length of the document. + * @param idf The inverse document frequency of the term. + * @return The BM25 relevance score for the term in the document. + */ + private double computeBM25Score(int termFrequency, double docLength, double idf) { + double numerator = termFrequency * (K + 1); + double denominator = termFrequency + K * (1 - B + B * (docLength / avgDocumentLength)); + return idf * (numerator / denominator); + } + + /** + * Compute the inverse document frequency (IDF) of a term. + * The IDF measures the importance of a term across the entire document set. + * @param docFrequency The number of documents that contain the term. + * @return The inverse document frequency (IDF) value. + */ + private double computeIDF(int docFrequency) { + // Total number of documents in the index + return Math.log((totalDocuments - docFrequency + 0.5) / (docFrequency + 0.5) + 1); + } +} diff --git a/src/main/java/com/thealgorithms/searches/BinarySearch.java b/src/main/java/com/thealgorithms/searches/BinarySearch.java index 22096307d144..bedad1667f33 100644 --- a/src/main/java/com/thealgorithms/searches/BinarySearch.java +++ b/src/main/java/com/thealgorithms/searches/BinarySearch.java @@ -1,10 +1,6 @@ package com.thealgorithms.searches; import com.thealgorithms.devutils.searches.SearchAlgorithm; -import java.util.Arrays; -import java.util.Random; -import java.util.concurrent.ThreadLocalRandom; -import java.util.stream.IntStream; /** * Binary search is one of the most popular algorithms The algorithm finds the @@ -57,26 +53,4 @@ private > int search(T[] array, T key, int left, int rig return search(array, key, median + 1, right); } } - - // Driver Program - public static void main(String[] args) { - // Just generate data - Random r = ThreadLocalRandom.current(); - - int size = 100; - int maxElement = 100000; - - Integer[] integers = IntStream.generate(() -> r.nextInt(maxElement)).limit(size).sorted().boxed().toArray(Integer[] ::new); - - // The element that should be found - int shouldBeFound = integers[r.nextInt(size - 1)]; - - BinarySearch search = new BinarySearch(); - int atIndex = search.find(integers, shouldBeFound); - - System.out.printf("Should be found: %d. Found %d at index %d. An array length %d%n", shouldBeFound, integers[atIndex], atIndex, size); - - int toCheck = Arrays.binarySearch(integers, shouldBeFound); - System.out.printf("Found by system method at an index: %d. Is equal: %b%n", toCheck, toCheck == atIndex); - } } diff --git a/src/main/java/com/thealgorithms/searches/BreadthFirstSearch.java b/src/main/java/com/thealgorithms/searches/BreadthFirstSearch.java index debab98c67a8..7ac9c7b01526 100644 --- a/src/main/java/com/thealgorithms/searches/BreadthFirstSearch.java +++ b/src/main/java/com/thealgorithms/searches/BreadthFirstSearch.java @@ -3,37 +3,54 @@ import com.thealgorithms.datastructures.Node; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Queue; +import java.util.Set; /** - * @author: caos321 - * @date: 31 October 2021 (Sunday) - * @wiki: https://en.wikipedia.org/wiki/Breadth-first_search + * Breadth-First Search implementation for tree/graph traversal. + * @author caos321 + * @co-author @manishraj27 + * @see Breadth-first search */ public class BreadthFirstSearch { - private final List visited = new ArrayList<>(); + private final Set visitedSet = new HashSet<>(); - public Optional> search(final Node node, final T value) { - if (node == null) { + /** + * Performs a breadth-first search to find a node with the given value. + * + * @param root The root node to start the search from + * @param value The value to search for + * @return Optional containing the found node, or empty if not found + */ + public Optional> search(final Node root, final T value) { + if (root == null) { return Optional.empty(); } - if (node.getValue().equals(value)) { - // add root node to visited - visited.add(value); - return Optional.of(node); - } - visited.add(node.getValue()); - Queue> queue = new ArrayDeque<>(node.getChildren()); + visited.add(root.getValue()); + visitedSet.add(root.getValue()); + + if (root.getValue() == value) { + return Optional.of(root); + } + Queue> queue = new ArrayDeque<>(root.getChildren()); while (!queue.isEmpty()) { final Node current = queue.poll(); - visited.add(current.getValue()); + T currentValue = current.getValue(); + + if (visitedSet.contains(currentValue)) { + continue; + } + + visited.add(currentValue); + visitedSet.add(currentValue); - if (current.getValue().equals(value)) { + if (currentValue == value || (value != null && value.equals(currentValue))) { return Optional.of(current); } @@ -43,6 +60,11 @@ public Optional> search(final Node node, final T value) { return Optional.empty(); } + /** + * Returns the list of nodes in the order they were visited. + * + * @return List containing the visited nodes + */ public List getVisited() { return visited; } diff --git a/src/main/java/com/thealgorithms/searches/ExponentalSearch.java b/src/main/java/com/thealgorithms/searches/ExponentalSearch.java index a856bd659720..9187dcbc2f4b 100644 --- a/src/main/java/com/thealgorithms/searches/ExponentalSearch.java +++ b/src/main/java/com/thealgorithms/searches/ExponentalSearch.java @@ -2,44 +2,47 @@ import com.thealgorithms.devutils.searches.SearchAlgorithm; import java.util.Arrays; -import java.util.Random; -import java.util.concurrent.ThreadLocalRandom; -import java.util.stream.IntStream; +/** + * ExponentialSearch is an algorithm that efficiently finds the position of a target + * value within a sorted array. It works by expanding the range to find the bounds + * where the target might exist and then using binary search within that range. + * + *

+ * Worst-case time complexity: O(log n) + * Best-case time complexity: O(1) when the element is found at the first position. + * Average time complexity: O(log n) + * Worst-case space complexity: O(1) + *

+ * + *

+ * Note: This algorithm requires that the input array be sorted. + *

+ */ class ExponentialSearch implements SearchAlgorithm { - public static void main(String[] args) { - Random r = ThreadLocalRandom.current(); - - int size = 100; - int maxElement = 100000; - - Integer[] integers = IntStream.generate(() -> r.nextInt(maxElement)).limit(size).sorted().boxed().toArray(Integer[] ::new); - - // The element that should be found - int shouldBeFound = integers[r.nextInt(size - 1)]; - - ExponentialSearch search = new ExponentialSearch(); - int atIndex = search.find(integers, shouldBeFound); - - System.out.printf("Should be found: %d. Found %d at index %d. An array length %d%n", shouldBeFound, integers[atIndex], atIndex, size); - - int toCheck = Arrays.binarySearch(integers, shouldBeFound); - System.out.printf("Found by system method at an index: %d. Is equal: %b%n", toCheck, toCheck == atIndex); - } - + /** + * Finds the index of the specified key in a sorted array using exponential search. + * + * @param array The sorted array to search. + * @param key The element to search for. + * @param The type of the elements in the array, which must be comparable. + * @return The index of the key if found, otherwise -1. + */ @Override public > int find(T[] array, T key) { - if (array[0] == key) { + if (array.length == 0) { + return -1; + } + if (array[0].equals(key)) { return 0; } - if (array[array.length - 1] == key) { - return array.length; + if (array[array.length - 1].equals(key)) { + return array.length - 1; } int range = 1; - - while (range < array.length && array[range].compareTo(key) <= -1) { + while (range < array.length && array[range].compareTo(key) < 0) { range = range * 2; } diff --git a/src/main/java/com/thealgorithms/searches/FibonacciSearch.java b/src/main/java/com/thealgorithms/searches/FibonacciSearch.java index 028ab07e0a86..2124938bc258 100644 --- a/src/main/java/com/thealgorithms/searches/FibonacciSearch.java +++ b/src/main/java/com/thealgorithms/searches/FibonacciSearch.java @@ -2,24 +2,42 @@ import com.thealgorithms.devutils.searches.SearchAlgorithm; -/* - * Fibonacci Search is a popular algorithm which finds the position of a target value in - * a sorted array +/** + * FibonacciSearch is a search algorithm that finds the position of a target value in + * a sorted array using Fibonacci numbers. * - * The time complexity for this search algorithm is O(log3(n)) - * The space complexity for this search algorithm is O(1) - * @author Kanakalatha Vemuru (https://github.com/KanakalathaVemuru) + *

+ * The time complexity for this search algorithm is O(log n). + * The space complexity for this search algorithm is O(1). + *

+ * + *

+ * Note: This algorithm requires that the input array be sorted. + *

*/ public class FibonacciSearch implements SearchAlgorithm { /** - * @param array is a sorted array where the element has to be searched - * @param key is an element whose position has to be found - * @param is any comparable type - * @return index of the element + * Finds the index of the specified key in a sorted array using Fibonacci search. + * + * @param array The sorted array to search. + * @param key The element to search for. + * @param The type of the elements in the array, which must be comparable. + * @throws IllegalArgumentException if the input array is not sorted or empty, or if the key is null. + * @return The index of the key if found, otherwise -1. */ @Override public > int find(T[] array, T key) { + if (array.length == 0) { + throw new IllegalArgumentException("Input array must not be empty."); + } + if (!isSorted(array)) { + throw new IllegalArgumentException("Input array must be sorted."); + } + if (key == null) { + throw new IllegalArgumentException("Key must not be null."); + } + int fibMinus1 = 1; int fibMinus2 = 0; int fibNumber = fibMinus1 + fibMinus2; @@ -57,15 +75,12 @@ public > int find(T[] array, T key) { return -1; } - // Driver Program - public static void main(String[] args) { - Integer[] integers = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512}; - - int size = integers.length; - Integer targetValue = 128; - FibonacciSearch fsearch = new FibonacciSearch(); - int atIndex = fsearch.find(integers, targetValue); - - System.out.println("Should be found: " + targetValue + ". Found " + integers[atIndex] + " at index " + atIndex + ". An array length " + size); + private boolean isSorted(Comparable[] array) { + for (int i = 1; i < array.length; i++) { + if (array[i - 1].compareTo(array[i]) > 0) { + return false; + } + } + return true; } } diff --git a/src/main/java/com/thealgorithms/searches/InterpolationSearch.java b/src/main/java/com/thealgorithms/searches/InterpolationSearch.java index aa1ff412b6a7..3ac6be25bf53 100644 --- a/src/main/java/com/thealgorithms/searches/InterpolationSearch.java +++ b/src/main/java/com/thealgorithms/searches/InterpolationSearch.java @@ -1,25 +1,31 @@ package com.thealgorithms.searches; -import java.util.Arrays; -import java.util.Random; -import java.util.stream.IntStream; - /** - * Interpolation search algorithm implementation + * InterpolationSearch is an algorithm that searches for a target value within a sorted array + * by estimating the position based on the values at the corners of the current search range. + * + *

+ * The performance of this algorithm can vary: + * - Worst-case performance: O(n) + * - Best-case performance: O(1) + * - Average performance: O(log(log(n))) if the elements are uniformly distributed; otherwise O(n) + * - Worst-case space complexity: O(1) + *

* *

- * Worst-case performance O(n) Best-case performance O(1) Average performance - * O(log(log(n))) if the elements are uniformly distributed if not O(n) - * Worst-case space complexity O(1) + * This search algorithm requires the input array to be sorted. + *

* * @author Podshivalov Nikita (https://github.com/nikitap492) */ class InterpolationSearch { /** - * @param array is a sorted array - * @param key is a value what shoulb be found in the array - * @return an index if the array contains the key unless -1 + * Finds the index of the specified key in a sorted array using interpolation search. + * + * @param array The sorted array to search. + * @param key The value to search for. + * @return The index of the key if found, otherwise -1. */ public int find(int[] array, int key) { // Find indexes of two corners @@ -48,23 +54,4 @@ public int find(int[] array, int key) { } return -1; } - - // Driver method - public static void main(String[] args) { - Random r = new Random(); - int size = 100; - int maxElement = 100000; - int[] integers = IntStream.generate(() -> r.nextInt(maxElement)).limit(size).sorted().toArray(); - - // the element that should be found - int shouldBeFound = integers[r.nextInt(size - 1)]; - - InterpolationSearch search = new InterpolationSearch(); - int atIndex = search.find(integers, shouldBeFound); - - System.out.printf("Should be found: %d. Found %d at index %d. An array length %d%n", shouldBeFound, integers[atIndex], atIndex, size); - - int toCheck = Arrays.binarySearch(integers, shouldBeFound); - System.out.printf("Found by system method at an index: %d. Is equal: %b%n", toCheck, toCheck == atIndex); - } } diff --git a/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java b/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java index 49a86e4e53a8..05fab0534267 100644 --- a/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java +++ b/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java @@ -1,9 +1,6 @@ package com.thealgorithms.searches; import com.thealgorithms.devutils.searches.SearchAlgorithm; -import java.util.Arrays; -import java.util.Random; -import java.util.stream.Stream; /** * Binary search is one of the most popular algorithms This class represents @@ -55,23 +52,4 @@ public > int find(T[] array, T key) { return -1; } - - // Only a main method for test purpose - public static void main(String[] args) { - Random r = new Random(); - int size = 100; - int maxElement = 100000; - Integer[] integers = Stream.generate(() -> r.nextInt(maxElement)).limit(size).sorted().toArray(Integer[] ::new); - - // the element that should be found - Integer shouldBeFound = integers[r.nextInt(size - 1)]; - - IterativeBinarySearch search = new IterativeBinarySearch(); - int atIndex = search.find(integers, shouldBeFound); - - System.out.printf("Should be found: %d. Found %d at index %d. An array length %d%n", shouldBeFound, integers[atIndex], atIndex, size); - - int toCheck = Arrays.binarySearch(integers, shouldBeFound); - System.out.printf("Found by system method at an index: %d. Is equal: %b%n", toCheck, toCheck == atIndex); - } } diff --git a/src/main/java/com/thealgorithms/searches/IterativeTernarySearch.java b/src/main/java/com/thealgorithms/searches/IterativeTernarySearch.java index e78acd6a7ef8..585d6787d3f8 100644 --- a/src/main/java/com/thealgorithms/searches/IterativeTernarySearch.java +++ b/src/main/java/com/thealgorithms/searches/IterativeTernarySearch.java @@ -1,22 +1,26 @@ package com.thealgorithms.searches; import com.thealgorithms.devutils.searches.SearchAlgorithm; -import java.util.Arrays; -import java.util.Random; -import java.util.stream.Stream; /** - * A iterative version of a ternary search algorithm This is better way to - * implement the ternary search, because a recursive version adds some overhead - * to a stack. But in java the compile can transform the recursive version to - * iterative implicitly, so there are no much differences between these two - * algorithms + * An iterative implementation of the Ternary Search algorithm. * *

- * Worst-case performance Θ(log3(N)) Best-case performance O(1) Average - * performance Θ(log3(N)) Worst-case space complexity O(1) + * Ternary search is a divide-and-conquer algorithm that splits the array into three parts + * instead of two, as in binary search. This implementation is iterative, reducing the overhead + * associated with recursive function calls. However, the recursive version can also be optimized + * by the Java compiler to resemble the iterative version, resulting in similar performance. + * + *

+ * Worst-case performance: Θ(log3(N))
+ * Best-case performance: O(1)
+ * Average performance: Θ(log3(N))
+ * Worst-case space complexity: O(1) + * + *

+ * This class implements the {@link SearchAlgorithm} interface, providing a generic search method + * for any comparable type. * - * @author Podshivalov Nikita (https://github.com/nikitap492) * @see SearchAlgorithm * @see TernarySearch * @since 2018-04-13 @@ -25,6 +29,13 @@ public class IterativeTernarySearch implements SearchAlgorithm { @Override public > int find(T[] array, T key) { + if (array == null || array.length == 0 || key == null) { + return -1; + } + if (array.length == 1) { + return array[0].compareTo(key) == 0 ? 0 : -1; + } + int left = 0; int right = array.length - 1; @@ -50,23 +61,4 @@ public > int find(T[] array, T key) { return -1; } - - public static void main(String[] args) { - // just generate data - Random r = new Random(); - int size = 100; - int maxElement = 100000; - Integer[] integers = Stream.generate(() -> r.nextInt(maxElement)).limit(size).sorted().toArray(Integer[] ::new); - - // the element that should be found - Integer shouldBeFound = integers[r.nextInt(size - 1)]; - - IterativeTernarySearch search = new IterativeTernarySearch(); - int atIndex = search.find(integers, shouldBeFound); - - System.out.printf("Should be found: %d. Found %d at index %d. An array length %d%n", shouldBeFound, integers[atIndex], atIndex, size); - - int toCheck = Arrays.binarySearch(integers, shouldBeFound); - System.out.printf("Found by system method at an index: %d. Is equal: %b%n", toCheck, toCheck == atIndex); - } } diff --git a/src/main/java/com/thealgorithms/searches/JumpSearch.java b/src/main/java/com/thealgorithms/searches/JumpSearch.java index f499cf8079cc..8dcec3a819a4 100644 --- a/src/main/java/com/thealgorithms/searches/JumpSearch.java +++ b/src/main/java/com/thealgorithms/searches/JumpSearch.java @@ -2,44 +2,55 @@ import com.thealgorithms.devutils.searches.SearchAlgorithm; +/** + * An implementation of the Jump Search algorithm. + * + *

+ * Jump Search is an algorithm for searching sorted arrays. It works by dividing the array + * into blocks of a fixed size (the block size is typically the square root of the array length) + * and jumping ahead by this block size to find a range where the target element may be located. + * 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. + * + *

+ * Worst-case performance: O(√N)
+ * Best-case performance: O(1)
+ * Average performance: O(√N)
+ * Worst-case space complexity: O(1) + * + *

+ * This class implements the {@link SearchAlgorithm} interface, providing a generic search method + * for any comparable type. + */ public class JumpSearch implements SearchAlgorithm { - public static void main(String[] args) { - JumpSearch jumpSearch = new JumpSearch(); - Integer[] array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - for (int i = 0; i < array.length; i++) { - assert jumpSearch.find(array, i) == i; - } - assert jumpSearch.find(array, -1) == -1; - assert jumpSearch.find(array, 11) == -1; - } - /** - * Jump Search algorithm implements + * Jump Search algorithm implementation. * - * @param array the array contains elements - * @param key to be searched - * @return index of {@code key} if found, otherwise -1 + * @param array the sorted array containing elements + * @param key the element to be searched + * @return the index of {@code key} if found, otherwise -1 */ @Override public > int find(T[] array, T key) { int length = array.length; - /* length of array */ int blockSize = (int) Math.sqrt(length); - /* block size to be jumped */ int limit = blockSize; - while (key.compareTo(array[limit]) > 0 && limit < array.length - 1) { - limit = Math.min(limit + blockSize, array.length - 1); + // 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); } - for (int i = limit - blockSize; i <= limit; i++) { - if (array[i] == key) { - /* execute linear search */ + // Perform linear search within the identified block + for (int i = limit - blockSize; i <= limit && i < length; i++) { + if (array[i].equals(key)) { return i; } } return -1; - /* not found */ } } diff --git a/src/main/java/com/thealgorithms/searches/LinearSearch.java b/src/main/java/com/thealgorithms/searches/LinearSearch.java index 57927b30a632..c7b70edb5112 100644 --- a/src/main/java/com/thealgorithms/searches/LinearSearch.java +++ b/src/main/java/com/thealgorithms/searches/LinearSearch.java @@ -1,8 +1,6 @@ package com.thealgorithms.searches; import com.thealgorithms.devutils.searches.SearchAlgorithm; -import java.util.Random; -import java.util.stream.Stream; /** * Linear search is the easiest search algorithm It works with sorted and @@ -36,20 +34,4 @@ public > int find(T[] array, T value) { } return -1; } - - public static void main(String[] args) { - // just generate data - Random r = new Random(); - int size = 200; - int maxElement = 100; - Integer[] integers = Stream.generate(() -> r.nextInt(maxElement)).limit(size).toArray(Integer[] ::new); - - // the element that should be found - Integer shouldBeFound = integers[r.nextInt(size - 1)]; - - LinearSearch search = new LinearSearch(); - int atIndex = search.find(integers, shouldBeFound); - - System.out.printf("Should be found: %d. Found %d at index %d. An array length %d%n", shouldBeFound, integers[atIndex], atIndex, size); - } } diff --git a/src/main/java/com/thealgorithms/searches/LinearSearchThread.java b/src/main/java/com/thealgorithms/searches/LinearSearchThread.java index b354d312d1b3..ba3cff915f5f 100644 --- a/src/main/java/com/thealgorithms/searches/LinearSearchThread.java +++ b/src/main/java/com/thealgorithms/searches/LinearSearchThread.java @@ -1,52 +1,47 @@ package com.thealgorithms.searches; -import java.util.Scanner; - +/** + * LinearSearchThread is a multithreaded implementation of the linear search algorithm. + * It creates multiple threads to search for a specific number in an array. + * + *

+ * The class generates an array of random integers, prompts the user to enter a number to search for, + * and divides the array into four segments, each handled by a separate thread. + * The threads run concurrently and search for the specified number within their designated segments. + * Finally, it consolidates the results to determine if the number was found. + *

+ * + *

+ * Example usage: + * 1. The program will output the generated array. + * 2. The user will be prompted to input a number to search for. + * 3. The program will display whether the number was found in the array. + *

+ */ public final class LinearSearchThread { private LinearSearchThread() { } - - public static void main(String[] args) { - int[] list = new int[200]; - for (int j = 0; j < list.length; j++) { - list[j] = (int) (Math.random() * 100); - } - for (int y : list) { - System.out.print(y + " "); - } - System.out.println(); - System.out.print("Enter number to search for: "); - Scanner in = new Scanner(System.in); - int x = in.nextInt(); - Searcher t = new Searcher(list, 0, 50, x); - Searcher t1 = new Searcher(list, 50, 100, x); - Searcher t2 = new Searcher(list, 100, 150, x); - Searcher t3 = new Searcher(list, 150, 200, x); - t.start(); - t1.start(); - t2.start(); - t3.start(); - try { - t.join(); - t1.join(); - t2.join(); - t3.join(); - } catch (InterruptedException e) { - } - boolean found = t.getResult() || t1.getResult() || t2.getResult() || t3.getResult(); - System.out.println("Found = " + found); - in.close(); - } } +/** + * The Searcher class extends Thread and is responsible for searching for a specific + * number in a segment of an array. + */ class Searcher extends Thread { + private final int[] arr; // The array to search in + private final int left; // Starting index of the segment + private final int right; // Ending index of the segment + private final int x; // The number to search for + private boolean found; // Result flag - private final int[] arr; - private final int left; - private final int right; - private final int x; - private boolean found; - + /** + * Constructor to initialize the Searcher. + * + * @param arr The array to search in + * @param left The starting index of the segment + * @param right The ending index of the segment + * @param x The number to search for + */ Searcher(int[] arr, int left, int right, int x) { this.arr = arr; this.left = left; @@ -54,6 +49,9 @@ class Searcher extends Thread { this.x = x; } + /** + * The run method for the thread, performing the linear search in its segment. + */ @Override public void run() { int k = left; @@ -65,6 +63,11 @@ public void run() { } } + /** + * Returns whether the number was found in the segment. + * + * @return true if the number was found, false otherwise + */ boolean getResult() { return found; } diff --git a/src/main/java/com/thealgorithms/searches/LowerBound.java b/src/main/java/com/thealgorithms/searches/LowerBound.java index ee6f51e637f2..5a1401edd3c2 100644 --- a/src/main/java/com/thealgorithms/searches/LowerBound.java +++ b/src/main/java/com/thealgorithms/searches/LowerBound.java @@ -1,9 +1,6 @@ package com.thealgorithms.searches; import com.thealgorithms.devutils.searches.SearchAlgorithm; -import java.util.Random; -import java.util.concurrent.ThreadLocalRandom; -import java.util.stream.IntStream; /** * The LowerBound method is used to return an index pointing to the first @@ -25,28 +22,6 @@ */ class LowerBound implements SearchAlgorithm { - // Driver Program - public static void main(String[] args) { - // Just generate data - Random r = ThreadLocalRandom.current(); - - int size = 100; - int maxElement = 100000; - - Integer[] integers = IntStream.generate(() -> r.nextInt(maxElement)).limit(size).sorted().boxed().toArray(Integer[] ::new); - - // The element for which the lower bound is to be found - int val = integers[r.nextInt(size - 1)] + 1; - - LowerBound search = new LowerBound(); - int atIndex = search.find(integers, val); - - System.out.printf("Val: %d. Lower Bound Found %d at index %d. An array length %d%n", val, integers[atIndex], atIndex, size); - - boolean toCheck = integers[atIndex] >= val || integers[size - 1] < val; - System.out.printf("Lower Bound found at an index: %d. Is greater or max element: %b%n", atIndex, toCheck); - } - /** * @param array is an array where the LowerBound value is to be found * @param key is an element for which the LowerBound is to be found diff --git a/src/main/java/com/thealgorithms/searches/MonteCarloTreeSearch.java b/src/main/java/com/thealgorithms/searches/MonteCarloTreeSearch.java index 268c33cef610..aa74398b708b 100644 --- a/src/main/java/com/thealgorithms/searches/MonteCarloTreeSearch.java +++ b/src/main/java/com/thealgorithms/searches/MonteCarloTreeSearch.java @@ -39,12 +39,6 @@ public Node(Node parent, boolean isPlayersTurn) { static final int WIN_SCORE = 10; static final int TIME_LIMIT = 500; // Time the algorithm will be running for (in milliseconds). - public static void main(String[] args) { - MonteCarloTreeSearch mcts = new MonteCarloTreeSearch(); - - mcts.monteCarloTreeSearch(mcts.new Node(null, true)); - } - /** * Explores a game tree using Monte Carlo Tree Search (MCTS) and returns the * most promising node. diff --git a/src/main/java/com/thealgorithms/searches/RandomSearch.java b/src/main/java/com/thealgorithms/searches/RandomSearch.java new file mode 100644 index 000000000000..3417ff7ddb21 --- /dev/null +++ b/src/main/java/com/thealgorithms/searches/RandomSearch.java @@ -0,0 +1,45 @@ +package com.thealgorithms.searches; + +import com.thealgorithms.devutils.searches.SearchAlgorithm; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; + +/** + * A Random Search algorithm that randomly selects an index and checks if the + * value at that index matches the target. It repeats the process until it + * finds the target or checks all elements. + * + *

+ * Time Complexity: O(n) in the worst case. + *

+ * + * @author Hardvan + */ +public class RandomSearch implements SearchAlgorithm { + + private final Random random = new Random(); + + /** + * Finds the index of a given element using random search. + * + * @param array Array to search through + * @param key Element to search for + * @return Index of the element if found, -1 otherwise + */ + @Override + public > int find(T[] array, T key) { + Set visitedIndices = new HashSet<>(); + int size = array.length; + + while (visitedIndices.size() < size) { + int randomIndex = random.nextInt(size); + if (array[randomIndex].compareTo(key) == 0) { + return randomIndex; + } + visitedIndices.add(randomIndex); + } + + return -1; + } +} diff --git a/src/main/java/com/thealgorithms/searches/SaddlebackSearch.java b/src/main/java/com/thealgorithms/searches/SaddlebackSearch.java index 5c7a914e3bf2..192c4d26c735 100644 --- a/src/main/java/com/thealgorithms/searches/SaddlebackSearch.java +++ b/src/main/java/com/thealgorithms/searches/SaddlebackSearch.java @@ -1,7 +1,5 @@ package com.thealgorithms.searches; -import java.util.Scanner; - /** * Program to perform Saddleback Search Given a sorted 2D array(elements are * sorted across every row and column, assuming ascending order) of size n*m we @@ -27,10 +25,15 @@ private SaddlebackSearch() { * @param row the current row. * @param col the current column. * @param key the element that we want to search for. + * @throws IllegalArgumentException if the array is empty. * @return The index(row and column) of the element if found. Else returns * -1 -1. */ - private static int[] find(int[][] arr, int row, int col, int key) { + static int[] find(int[][] arr, int row, int col, int key) { + if (arr.length == 0) { + throw new IllegalArgumentException("Array is empty"); + } + // array to store the answer row and column int[] ans = {-1, -1}; if (row < 0 || col >= arr[row].length) { @@ -47,30 +50,4 @@ else if (arr[row][col] > key) { // else we move right return find(arr, row, col + 1, key); } - - /** - * Main method - * - * @param args Command line arguments - */ - public static void main(String[] args) { - // TODO Auto-generated method stub - Scanner sc = new Scanner(System.in); - int[][] arr; - int i; - int j; - int rows = sc.nextInt(); - int col = sc.nextInt(); - arr = new int[rows][col]; - for (i = 0; i < rows; i++) { - for (j = 0; j < col; j++) { - arr[i][j] = sc.nextInt(); - } - } - int ele = sc.nextInt(); - // we start from bottom left corner - int[] ans = find(arr, rows - 1, 0, ele); - System.out.println(ans[0] + " " + ans[1]); - sc.close(); - } } diff --git a/src/main/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrix.java b/src/main/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrix.java index 2bdcdb48b653..b53c7e5256ca 100644 --- a/src/main/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrix.java +++ b/src/main/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrix.java @@ -8,7 +8,6 @@ public class SearchInARowAndColWiseSortedMatrix { * @param value Key being searched for * @author Sadiul Hakim : https://github.com/sadiul-hakim */ - public int[] search(int[][] matrix, int value) { int n = matrix.length; // This variable iterates over rows @@ -16,7 +15,6 @@ public int[] search(int[][] matrix, int value) { // This variable iterates over columns int j = n - 1; int[] result = {-1, -1}; - while (i < n && j >= 0) { if (matrix[i][j] == value) { result[0] = i; diff --git a/src/main/java/com/thealgorithms/searches/SquareRootBinarySearch.java b/src/main/java/com/thealgorithms/searches/SquareRootBinarySearch.java index c00bfc9da6f5..95e062c274fd 100644 --- a/src/main/java/com/thealgorithms/searches/SquareRootBinarySearch.java +++ b/src/main/java/com/thealgorithms/searches/SquareRootBinarySearch.java @@ -1,7 +1,5 @@ package com.thealgorithms.searches; -import java.util.Scanner; - /** * Given an integer x, find the square root of x. If x is not a perfect square, * then return floor(√x). @@ -18,20 +16,6 @@ public final class SquareRootBinarySearch { private SquareRootBinarySearch() { } - /** - * This is the driver method. - * - * @param args Command line arguments - */ - public static void main(String[] args) { - Scanner sc = new Scanner(System.in); - System.out.print("Enter a number you want to calculate square root of : "); - int num = sc.nextInt(); - long ans = squareRoot(num); - System.out.println("The square root is : " + ans); - sc.close(); - } - /** * This function calculates the floor of square root of a number. We use * Binary Search algorithm to calculate the square root in a more optimised @@ -40,7 +24,7 @@ public static void main(String[] args) { * @param num Number * @return answer */ - private static long squareRoot(long num) { + static long squareRoot(long num) { if (num == 0 || num == 1) { return num; } diff --git a/src/main/java/com/thealgorithms/searches/TernarySearch.java b/src/main/java/com/thealgorithms/searches/TernarySearch.java index 3395bc0b7f30..4d9f55ea9917 100644 --- a/src/main/java/com/thealgorithms/searches/TernarySearch.java +++ b/src/main/java/com/thealgorithms/searches/TernarySearch.java @@ -1,9 +1,6 @@ package com.thealgorithms.searches; import com.thealgorithms.devutils.searches.SearchAlgorithm; -import java.util.Arrays; -import java.util.Random; -import java.util.stream.Stream; /** * A ternary search algorithm is a technique in computer science for finding the @@ -60,23 +57,4 @@ private > int ternarySearch(T[] arr, T key, int start, i return ternarySearch(arr, key, mid1, mid2); } } - - public static void main(String[] args) { - // just generate data - Random r = new Random(); - int size = 100; - int maxElement = 100000; - Integer[] integers = Stream.generate(() -> r.nextInt(maxElement)).limit(size).sorted().toArray(Integer[] ::new); - - // the element that should be found - Integer shouldBeFound = integers[r.nextInt(size - 1)]; - - TernarySearch search = new TernarySearch(); - int atIndex = search.find(integers, shouldBeFound); - - System.out.printf("Should be found: %d. Found %d at index %d. An array length %d%n", shouldBeFound, integers[atIndex], atIndex, size); - - int toCheck = Arrays.binarySearch(integers, shouldBeFound); - System.out.printf("Found by system method at an index: %d. Is equal: %b%n", toCheck, toCheck == atIndex); - } } diff --git a/src/main/java/com/thealgorithms/searches/UnionFind.java b/src/main/java/com/thealgorithms/searches/UnionFind.java index 2effdf37bea5..01202a982266 100644 --- a/src/main/java/com/thealgorithms/searches/UnionFind.java +++ b/src/main/java/com/thealgorithms/searches/UnionFind.java @@ -4,11 +4,28 @@ import java.util.Arrays; import java.util.List; +/** + * The Union-Find data structure, also known as Disjoint Set Union (DSU), + * is a data structure that tracks a set of elements partitioned into + * disjoint (non-overlapping) subsets. It supports two main operations: + * + * 1. **Find**: Determine which subset a particular element is in. + * 2. **Union**: Join two subsets into a single subset. + * + * This implementation uses path compression in the `find` operation + * and union by rank in the `union` operation for efficiency. + */ public class UnionFind { - private final int[] p; - private final int[] r; + private final int[] p; // Parent array + private final int[] r; // Rank array + /** + * Initializes a Union-Find data structure with n elements. + * Each element is its own parent initially. + * + * @param n the number of elements + */ public UnionFind(int n) { p = new int[n]; r = new int[n]; @@ -18,6 +35,13 @@ public UnionFind(int n) { } } + /** + * Finds the root of the set containing the element i. + * Uses path compression to flatten the structure. + * + * @param i the element to find + * @return the root of the set + */ public int find(int i) { int parent = p[i]; @@ -25,12 +49,19 @@ public int find(int i) { return i; } + // Path compression final int result = find(parent); p[i] = result; - return result; } + /** + * Unites the sets containing elements x and y. + * Uses union by rank to attach the smaller tree under the larger tree. + * + * @param x the first element + * @param y the second element + */ public void union(int x, int y) { int r0 = find(x); int r1 = find(y); @@ -39,6 +70,7 @@ public void union(int x, int y) { return; } + // Union by rank if (r[r0] > r[r1]) { p[r1] = r0; } else if (r[r1] > r[r0]) { @@ -49,39 +81,24 @@ public void union(int x, int y) { } } + /** + * Counts the number of disjoint sets. + * + * @return the number of disjoint sets + */ public int count() { List parents = new ArrayList<>(); for (int i = 0; i < p.length; i++) { - if (!parents.contains(find(i))) { - parents.add(find(i)); + int root = find(i); + if (!parents.contains(root)) { + parents.add(root); } } return parents.size(); } + @Override public String toString() { return "p " + Arrays.toString(p) + " r " + Arrays.toString(r) + "\n"; } - - // Tests - public static void main(String[] args) { - UnionFind uf = new UnionFind(5); - System.out.println("init /w 5 (should print 'p [0, 1, 2, 3, 4] r [0, 0, 0, 0, 0]'):"); - System.out.println(uf); - - uf.union(1, 2); - System.out.println("union 1 2 (should print 'p [0, 1, 1, 3, 4] r [0, 1, 0, 0, 0]'):"); - System.out.println(uf); - - uf.union(3, 4); - System.out.println("union 3 4 (should print 'p [0, 1, 1, 3, 3] r [0, 1, 0, 1, 0]'):"); - System.out.println(uf); - - uf.find(4); - System.out.println("find 4 (should print 'p [0, 1, 1, 3, 3] r [0, 1, 0, 1, 0]'):"); - System.out.println(uf); - - System.out.println("count (should print '3'):"); - System.out.println(uf.count()); - } } diff --git a/src/main/java/com/thealgorithms/searches/UpperBound.java b/src/main/java/com/thealgorithms/searches/UpperBound.java index bbce617a143b..ec52c7a0ae5c 100644 --- a/src/main/java/com/thealgorithms/searches/UpperBound.java +++ b/src/main/java/com/thealgorithms/searches/UpperBound.java @@ -1,9 +1,6 @@ package com.thealgorithms.searches; import com.thealgorithms.devutils.searches.SearchAlgorithm; -import java.util.Random; -import java.util.concurrent.ThreadLocalRandom; -import java.util.stream.IntStream; /** * The UpperBound method is used to return an index pointing to the first @@ -25,28 +22,6 @@ */ class UpperBound implements SearchAlgorithm { - // Driver Program - public static void main(String[] args) { - // Just generate data - Random r = ThreadLocalRandom.current(); - - int size = 100; - int maxElement = 100000; - - Integer[] integers = IntStream.generate(() -> r.nextInt(maxElement)).limit(size).sorted().boxed().toArray(Integer[] ::new); - - // The element for which the upper bound is to be found - int val = integers[r.nextInt(size - 1)] + 1; - - UpperBound search = new UpperBound(); - int atIndex = search.find(integers, val); - - System.out.printf("Val: %d. Upper Bound Found %d at index %d. An array length %d%n", val, integers[atIndex], atIndex, size); - - boolean toCheck = integers[atIndex] > val || integers[size - 1] < val; - System.out.printf("Upper Bound found at an index: %d. Is greater or max element: %b%n", atIndex, toCheck); - } - /** * @param array is an array where the UpperBound value is to be found * @param key is an element for which the UpperBound is to be found diff --git a/src/main/java/com/thealgorithms/slidingwindow/LongestSubarrayWithSumLessOrEqualToK.java b/src/main/java/com/thealgorithms/slidingwindow/LongestSubarrayWithSumLessOrEqualToK.java new file mode 100644 index 000000000000..55c3f709b467 --- /dev/null +++ b/src/main/java/com/thealgorithms/slidingwindow/LongestSubarrayWithSumLessOrEqualToK.java @@ -0,0 +1,48 @@ +package com.thealgorithms.slidingwindow; + +/** + * The Longest Subarray with Sum Less Than or Equal to k algorithm finds the length + * of the longest subarray whose sum is less than or equal to a given value k. + * + *

+ * Worst-case performance O(n) + * Best-case performance O(n) + * Average performance O(n) + * Worst-case space complexity O(1) + * + * @author https://github.com/Chiefpatwal + */ +public final class LongestSubarrayWithSumLessOrEqualToK { + + // Prevent instantiation + private LongestSubarrayWithSumLessOrEqualToK() { + } + + /** + * This method finds the length of the longest subarray with a sum less than or equal to k. + * + * @param arr is the input array + * @param k is the maximum sum allowed + * @return the length of the longest subarray with sum less than or equal to k + */ + public static int longestSubarrayWithSumLEK(int[] arr, int k) { + int maxLength = 0; // To store the maximum length found + int currentSum = 0; // To store the current sum of the window + int left = 0; // Left index of the sliding window + + for (int right = 0; right < arr.length; right++) { + currentSum += arr[right]; // Expand the window to the right + + // Shrink the window from the left if the current sum exceeds k + while (currentSum > k && left <= right) { + currentSum -= arr[left]; // Remove the leftmost element + left++; // Move the left index to the right + } + + // Update maxLength if the current window is valid + maxLength = Math.max(maxLength, right - left + 1); + } + + return maxLength; // Return the maximum length found + } +} diff --git a/src/main/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharacters.java b/src/main/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharacters.java new file mode 100644 index 000000000000..0641730d8b09 --- /dev/null +++ b/src/main/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharacters.java @@ -0,0 +1,46 @@ +package com.thealgorithms.slidingwindow; +import java.util.HashSet; + +/** + * The Longest Substring Without Repeating Characters algorithm finds the length of + * the longest substring without repeating characters in a given string. + * + *

+ * Worst-case performance O(n) + * Best-case performance O(n) + * Average performance O(n) + * Worst-case space complexity O(min(n, m)), where n is the length of the string + * and m is the size of the character set. + * + * @author (https://github.com/Chiefpatwal) + */ +public final class LongestSubstringWithoutRepeatingCharacters { + + // Prevent instantiation + private LongestSubstringWithoutRepeatingCharacters() { + } + + /** + * This method finds the length of the longest substring without repeating characters. + * + * @param s is the input string + * @return the length of the longest substring without repeating characters + */ + public static int lengthOfLongestSubstring(String s) { + int maxLength = 0; + int left = 0; + HashSet charSet = new HashSet<>(); + + for (int right = 0; right < s.length(); right++) { + // If the character is already in the set, remove characters from the left + while (charSet.contains(s.charAt(right))) { + charSet.remove(s.charAt(left)); + left++; + } + charSet.add(s.charAt(right)); + maxLength = Math.max(maxLength, right - left + 1); + } + + return maxLength; + } +} diff --git a/src/main/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarray.java b/src/main/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarray.java new file mode 100644 index 000000000000..7e8095e08a0b --- /dev/null +++ b/src/main/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarray.java @@ -0,0 +1,50 @@ +package com.thealgorithms.slidingwindow; + +/** + * The Sliding Window algorithm is used to find the maximum sum of a subarray + * of a fixed size k within a given array. + * + *

+ * Worst-case performance O(n) + * Best-case performance O(n) + * Average performance O(n) + * Worst-case space complexity O(1) + * + * @author Your Name (https://github.com/Chiefpatwal) + */ +public final class MaxSumKSizeSubarray { + + // Prevent instantiation + private MaxSumKSizeSubarray() { + } + + /** + * This method finds the maximum sum of a subarray of a given size k. + * + * @param arr is the input array where the maximum sum needs to be found + * @param k is the size of the subarray + * @return the maximum sum of the subarray of size k + */ + public static int maxSumKSizeSubarray(int[] arr, int k) { + if (arr.length < k) { + return -1; // Edge case: not enough elements + } + + int maxSum; + int windowSum = 0; + + // Calculate the sum of the first window + for (int i = 0; i < k; i++) { + windowSum += arr[i]; + } + maxSum = windowSum; + + // Slide the window across the array + for (int i = k; i < arr.length; i++) { + windowSum += arr[i] - arr[i - k]; + maxSum = Math.max(maxSum, windowSum); + } + + return maxSum; + } +} diff --git a/src/main/java/com/thealgorithms/slidingwindow/MinSumKSizeSubarray.java b/src/main/java/com/thealgorithms/slidingwindow/MinSumKSizeSubarray.java new file mode 100644 index 000000000000..40a5441fa7a0 --- /dev/null +++ b/src/main/java/com/thealgorithms/slidingwindow/MinSumKSizeSubarray.java @@ -0,0 +1,51 @@ +package com.thealgorithms.slidingwindow; +/** + * The Sliding Window algorithm is used to find the minimum sum of a subarray + * of a fixed size k within a given array. + * + *

+ * Worst-case performance O(n) + * Best-case performance O(n) + * Average performance O(n) + * Worst-case space complexity O(1) + * + * This class provides a static method to find the minimum sum of a subarray + * with a specified length k. + * + * @author Rashi Dashore (https://github.com/rashi07dashore) + */ +public final class MinSumKSizeSubarray { + + // Prevent instantiation + private MinSumKSizeSubarray() { + } + + /** + * This method finds the minimum sum of a subarray of a given size k. + * + * @param arr is the input array where the minimum sum needs to be found + * @param k is the size of the subarray + * @return the minimum sum of the subarray of size k + */ + public static int minSumKSizeSubarray(int[] arr, int k) { + if (arr.length < k) { + return -1; // Edge case: not enough elements + } + + int minSum; + int windowSum = 0; + + // Calculate the sum of the first window + for (int i = 0; i < k; i++) { + windowSum += arr[i]; + } + minSum = windowSum; + + // Slide the window across the array + for (int i = k; i < arr.length; i++) { + windowSum += arr[i] - arr[i - k]; + minSum = Math.min(minSum, windowSum); + } + return minSum; + } +} diff --git a/src/main/java/com/thealgorithms/sorts/AdaptiveMergeSort.java b/src/main/java/com/thealgorithms/sorts/AdaptiveMergeSort.java new file mode 100644 index 000000000000..2c71bae8b557 --- /dev/null +++ b/src/main/java/com/thealgorithms/sorts/AdaptiveMergeSort.java @@ -0,0 +1,40 @@ +package com.thealgorithms.sorts; + +public class AdaptiveMergeSort implements SortAlgorithm { + @SuppressWarnings("unchecked") + public > T[] sort(T[] array) { + if (array.length <= 1) { + return array; + } + T[] aux = array.clone(); + sort(array, aux, 0, array.length - 1); + return array; + } + + private > void sort(T[] array, T[] aux, int low, int high) { + if (low >= high) { + return; + } + int mid = low + (high - low) / 2; + sort(array, aux, low, mid); + sort(array, aux, mid + 1, high); + merge(array, aux, low, mid, high); + } + + private > void merge(T[] array, T[] aux, int low, int mid, int high) { + System.arraycopy(array, low, aux, low, high - low + 1); + int i = low; + int j = mid + 1; + for (int k = low; k <= high; k++) { + if (i > mid) { + array[k] = aux[j++]; + } else if (j > high) { + array[k] = aux[i++]; + } else if (aux[j].compareTo(aux[i]) < 0) { + array[k] = aux[j++]; + } else { + array[k] = aux[i++]; + } + } + } +} diff --git a/src/main/java/com/thealgorithms/sorts/BucketSort.java b/src/main/java/com/thealgorithms/sorts/BucketSort.java index a6901ac339ac..62c5e929593b 100644 --- a/src/main/java/com/thealgorithms/sorts/BucketSort.java +++ b/src/main/java/com/thealgorithms/sorts/BucketSort.java @@ -79,7 +79,7 @@ private > void distributeElementsIntoBuckets(T[] array, * @param the type of elements in the array * @return the sorted array */ - private > T[] concatenateBuckets(List> buckets, T[] array) { + private > T[] concatenateBuckets(Iterable> buckets, T[] array) { int index = 0; for (List bucket : buckets) { Collections.sort(bucket); diff --git a/src/main/java/com/thealgorithms/sorts/DarkSort.java b/src/main/java/com/thealgorithms/sorts/DarkSort.java new file mode 100644 index 000000000000..4887d7d124ba --- /dev/null +++ b/src/main/java/com/thealgorithms/sorts/DarkSort.java @@ -0,0 +1,59 @@ +package com.thealgorithms.sorts; + +/** + * Dark Sort algorithm implementation. + * + * Dark Sort uses a temporary array to count occurrences of elements and + * reconstructs the sorted array based on the counts. + */ +class DarkSort { + + /** + * Sorts the array using the Dark Sort algorithm. + * + * @param unsorted the array to be sorted + * @return sorted array + */ + public Integer[] sort(Integer[] unsorted) { + if (unsorted == null || unsorted.length <= 1) { + return unsorted; + } + + int max = findMax(unsorted); // Find the maximum value in the array + + // Create a temporary array for counting occurrences + int[] temp = new int[max + 1]; + + // Count occurrences of each element + for (int value : unsorted) { + temp[value]++; + } + + // Reconstruct the sorted array + int index = 0; + for (int i = 0; i < temp.length; i++) { + while (temp[i] > 0) { + unsorted[index++] = i; + temp[i]--; + } + } + + return unsorted; + } + + /** + * Helper method to find the maximum value in an array. + * + * @param arr the array + * @return the maximum value + */ + private int findMax(Integer[] arr) { + int max = arr[0]; + for (int value : arr) { + if (value > max) { + max = value; + } + } + return max; + } +} diff --git a/src/main/java/com/thealgorithms/sorts/LinkListSort.java b/src/main/java/com/thealgorithms/sorts/LinkListSort.java index bf8910d94eae..800d78f36549 100644 --- a/src/main/java/com/thealgorithms/sorts/LinkListSort.java +++ b/src/main/java/com/thealgorithms/sorts/LinkListSort.java @@ -259,14 +259,12 @@ static int count(Node head) { class Task2 { - private int[] a; - public Node sortByHeapSort(Node head) { if (head == null || head.next == null) { return head; } int c = count(head); - a = new int[c]; + int[] a = new int[c]; // Array of size c is created int i = 0; for (Node ptr = head; ptr != null; ptr = ptr.next) { diff --git a/src/main/java/com/thealgorithms/sorts/PatienceSort.java b/src/main/java/com/thealgorithms/sorts/PatienceSort.java index 52ed30d586b3..0edce8d9a15d 100644 --- a/src/main/java/com/thealgorithms/sorts/PatienceSort.java +++ b/src/main/java/com/thealgorithms/sorts/PatienceSort.java @@ -72,7 +72,7 @@ private static > List> formPiles(final T[] array * @param the type of elements in the piles, must be comparable * @return a priority queue containing the top element of each pile */ - private static > PriorityQueue> mergePiles(final List> piles) { + private static > PriorityQueue> mergePiles(final Iterable> piles) { PriorityQueue> pq = new PriorityQueue<>(); for (List pile : piles) { pq.add(new PileNode<>(pile.removeLast(), pile)); diff --git a/src/main/java/com/thealgorithms/sorts/PigeonholeSort.java b/src/main/java/com/thealgorithms/sorts/PigeonholeSort.java index 78d7d81d709f..19f4291d8213 100644 --- a/src/main/java/com/thealgorithms/sorts/PigeonholeSort.java +++ b/src/main/java/com/thealgorithms/sorts/PigeonholeSort.java @@ -78,7 +78,7 @@ private static void populatePigeonHoles(int[] array, List> pigeonH * @param array the array to be sorted * @param pigeonHoles the populated pigeonholes */ - private static void collectFromPigeonHoles(int[] array, List> pigeonHoles) { + private static void collectFromPigeonHoles(int[] array, Iterable> pigeonHoles) { int index = 0; for (final var pigeonHole : pigeonHoles) { for (final int element : pigeonHole) { diff --git a/src/main/java/com/thealgorithms/sorts/StalinSort.java b/src/main/java/com/thealgorithms/sorts/StalinSort.java new file mode 100644 index 000000000000..5aaf530fd94c --- /dev/null +++ b/src/main/java/com/thealgorithms/sorts/StalinSort.java @@ -0,0 +1,21 @@ +package com.thealgorithms.sorts; + +public class StalinSort implements SortAlgorithm { + @SuppressWarnings("unchecked") + public > T[] sort(T[] array) { + if (array.length == 0) { + return array; + } + int currentIndex = 0; + for (int i = 1; i < array.length; i++) { + if (array[i].compareTo(array[currentIndex]) >= 0) { + currentIndex++; + array[currentIndex] = array[i]; + } + } + // Create a result array with sorted elements + T[] result = (T[]) java.lang.reflect.Array.newInstance(array.getClass().getComponentType(), currentIndex + 1); + System.arraycopy(array, 0, result, 0, currentIndex + 1); + return result; + } +} diff --git a/src/main/java/com/thealgorithms/sorts/TreeSort.java b/src/main/java/com/thealgorithms/sorts/TreeSort.java index e060af542f98..6f4e5509489d 100644 --- a/src/main/java/com/thealgorithms/sorts/TreeSort.java +++ b/src/main/java/com/thealgorithms/sorts/TreeSort.java @@ -51,7 +51,7 @@ private > T[] doTreeSortArray(T[] unsortedArray) { return unsortedArray; } - private > List doTreeSortList(List unsortedList) { + private > List doTreeSortList(Iterable unsortedList) { // create a generic BST tree BSTRecursiveGeneric tree = new BSTRecursiveGeneric(); diff --git a/src/main/java/com/thealgorithms/stacks/CelebrityFinder.java b/src/main/java/com/thealgorithms/stacks/CelebrityFinder.java new file mode 100644 index 000000000000..67ac861ef82b --- /dev/null +++ b/src/main/java/com/thealgorithms/stacks/CelebrityFinder.java @@ -0,0 +1,52 @@ +package com.thealgorithms.stacks; + +import java.util.Stack; + +/** + * Solves the celebrity problem using a stack-based algorithm. + * + *

Celebrity is someone known by everyone but doesn't know anyone else. + *

Applications: Graph theory and social network analysis. + * + * @author Hardvan + */ +public final class CelebrityFinder { + private CelebrityFinder() { + } + + /** + * Finds the celebrity in the given party matrix using a stack-based algorithm. + * + * @param party A 2D matrix where party[i][j] is 1 if i knows j, otherwise 0. + * @return The index of the celebrity, or -1 if there is no celebrity. + */ + public static int findCelebrity(int[][] party) { + + // Push all people onto the stack + Stack stack = new Stack<>(); + for (int i = 0; i < party.length; i++) { + stack.push(i); + } + + // Find the potential celebrity by comparing pairs + while (stack.size() > 1) { + int person1 = stack.pop(); + int person2 = stack.pop(); + + if (party[person1][person2] == 1) { + stack.push(person2); // person1 knows person2, so person2 might be the celebrity + } else { + stack.push(person1); // person1 doesn't know person2, so person1 might be the celebrity + } + } + + // Verify the candidate + int candidate = stack.pop(); + for (int i = 0; i < party.length; i++) { + if (i != candidate && (party[candidate][i] == 1 || party[i][candidate] == 0)) { + return -1; + } + } + return candidate; + } +} diff --git a/src/main/java/com/thealgorithms/stacks/GreatestElementConstantTime.java b/src/main/java/com/thealgorithms/stacks/GreatestElementConstantTime.java new file mode 100644 index 000000000000..be9d0099d38b --- /dev/null +++ b/src/main/java/com/thealgorithms/stacks/GreatestElementConstantTime.java @@ -0,0 +1,74 @@ +package com.thealgorithms.stacks; + +import java.util.NoSuchElementException; +import java.util.Stack; + +/** + * A class that implements a stack that gives the maximum element in O(1) time. + * The mainStack is used to store the all the elements of the stack + * While the maxStack stores the maximum elements + * When we want to get a maximum element, we call the top of the maximum stack + * + * Problem: https://www.baeldung.com/cs/stack-constant-time + */ +public class GreatestElementConstantTime { + private Stack mainStack; // initialize a mainStack + private Stack maxStack; // initialize a maxStack + + /** + * Constructs two empty stacks + */ + public GreatestElementConstantTime() { + mainStack = new Stack<>(); + maxStack = new Stack<>(); + } + + /** + * Pushes an element onto the top of the stack. + * Checks if the element is the maximum or not + * If so, then pushes to the maximum stack + * @param data The element to be pushed onto the stack. + */ + public void push(int data) { + if (mainStack.isEmpty()) { + mainStack.push(data); + maxStack.push(data); + return; + } + + mainStack.push(data); + if (data > maxStack.peek()) { + maxStack.push(data); + } + } + + /** + * Pops an element from the stack. + * Checks if the element to be popped is the maximum or not + * If so, then pop from the minStack + * + * @throws NoSuchElementException if the stack is empty. + */ + public void pop() { + if (mainStack.isEmpty()) { + throw new NoSuchElementException("Stack is empty"); + } + + int ele = mainStack.pop(); + if (ele == maxStack.peek()) { + maxStack.pop(); + } + } + + /** + * Returns the maximum element present in the stack + * + * @return The element at the top of the maxStack, or null if the stack is empty. + */ + public Integer getMaximumElement() { + if (maxStack.isEmpty()) { + return null; + } + return maxStack.peek(); + } +} diff --git a/src/main/java/com/thealgorithms/stacks/MinStackUsingSingleStack.java b/src/main/java/com/thealgorithms/stacks/MinStackUsingSingleStack.java new file mode 100644 index 000000000000..f5e526b102cf --- /dev/null +++ b/src/main/java/com/thealgorithms/stacks/MinStackUsingSingleStack.java @@ -0,0 +1,65 @@ +package com.thealgorithms.stacks; + +import java.util.EmptyStackException; +import java.util.Stack; + +/** + * Min-Stack implementation using a single stack. + * + * This stack supports push, pop, and retrieving the minimum element + * in constant time (O(1)) using a modified approach where the stack + * stores both the element and the minimum value so far. + * + * @author Hardvan + */ +public class MinStackUsingSingleStack { + private final Stack stack = new Stack<>(); + + /** + * Pushes a new value onto the stack. + * Each entry stores both the value and the minimum value so far. + * + * @param value The value to be pushed onto the stack. + */ + public void push(int value) { + if (stack.isEmpty()) { + stack.push(new long[] {value, value}); + } else { + long minSoFar = Math.min(value, stack.peek()[1]); + stack.push(new long[] {value, minSoFar}); + } + } + + /** + * Removes the top element from the stack. + */ + public void pop() { + if (!stack.isEmpty()) { + stack.pop(); + } + } + + /** + * Retrieves the top element from the stack. + * + * @return The top element of the stack. + */ + public int top() { + if (!stack.isEmpty()) { + return (int) stack.peek()[0]; + } + throw new EmptyStackException(); + } + + /** + * Retrieves the minimum element in the stack. + * + * @return The minimum element so far. + */ + public int getMin() { + if (!stack.isEmpty()) { + return (int) stack.peek()[1]; + } + throw new EmptyStackException(); + } +} diff --git a/src/main/java/com/thealgorithms/stacks/MinStackUsingTwoStacks.java b/src/main/java/com/thealgorithms/stacks/MinStackUsingTwoStacks.java new file mode 100644 index 000000000000..47e337c80b59 --- /dev/null +++ b/src/main/java/com/thealgorithms/stacks/MinStackUsingTwoStacks.java @@ -0,0 +1,57 @@ +package com.thealgorithms.stacks; + +import java.util.Stack; + +/** + * Min-Stack implementation that supports push, pop, and retrieving the minimum element in constant time. + * + * @author Hardvan + */ +public final class MinStackUsingTwoStacks { + MinStackUsingTwoStacks() { + } + + private final Stack stack = new Stack<>(); + private final Stack minStack = new Stack<>(); + + /** + * Pushes a new element onto the {@code stack}. + * If the value is less than or equal to the current minimum, it is also pushed onto the {@code minStack}. + * + * @param value The value to be pushed. + */ + public void push(int value) { + stack.push(value); + if (minStack.isEmpty() || value <= minStack.peek()) { + minStack.push(value); + } + } + + /** + * Removes the top element from the stack. + * If the element is the minimum element, it is also removed from the {@code minStack}. + */ + public void pop() { + if (stack.pop().equals(minStack.peek())) { + minStack.pop(); + } + } + + /** + * Retrieves the top element of the stack. + * + * @return The top element. + */ + public int top() { + return stack.peek(); + } + + /** + * Retrieves the minimum element in the stack. + * + * @return The minimum element. + */ + public int getMin() { + return minStack.peek(); + } +} diff --git a/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java b/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java new file mode 100644 index 000000000000..98c439341a21 --- /dev/null +++ b/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java @@ -0,0 +1,57 @@ +package com.thealgorithms.stacks; + +import java.util.LinkedList; + +/** + * A class that implements a palindrome checker using a stack. + * The stack is used to store the characters of the string, + * 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/ + */ +public class PalindromeWithStack { + private LinkedList stack; + + /** + * Constructs an empty stack that stores characters. + */ + public PalindromeWithStack() { + stack = new LinkedList(); + } + + /** + * Check if the string is a palindrome or not. + * Convert all characters to lowercase and push them into a stack. + * At the same time, build a string + * Next, pop from the stack and build the reverse string + * Finally, compare these two strings + * + * @param string The string to check if it is palindrome or not. + */ + public boolean checkPalindrome(String string) { + // Create a StringBuilder to build the string from left to right + StringBuilder stringBuilder = new StringBuilder(string.length()); + // Convert all characters to lowercase + String lowercase = string.toLowerCase(); + + // Iterate through the string + for (int i = 0; i < lowercase.length(); ++i) { + char c = lowercase.charAt(i); + // Build the string from L->R + stringBuilder.append(c); + // Push to the stack + stack.push(c); + } + + // The stack contains the reverse order of the string + StringBuilder reverseString = new StringBuilder(stack.size()); + // Until the stack is not empty + while (!stack.isEmpty()) { + // Build the string from R->L + reverseString.append(stack.pop()); + } + + // Finally, compare the L->R string with the R->L string + return reverseString.toString().equals(stringBuilder.toString()); + } +} diff --git a/src/main/java/com/thealgorithms/stacks/PostfixEvaluator.java b/src/main/java/com/thealgorithms/stacks/PostfixEvaluator.java new file mode 100644 index 000000000000..02ef5af8da16 --- /dev/null +++ b/src/main/java/com/thealgorithms/stacks/PostfixEvaluator.java @@ -0,0 +1,74 @@ +package com.thealgorithms.stacks; + +import java.util.Set; +import java.util.Stack; + +/** + * Evaluate a postfix (Reverse Polish) expression using a stack. + * + *

Example: Expression "5 6 + 2 *" results in 22. + *

Applications: Used in calculators and expression evaluation in compilers. + * + * @author Hardvan + */ +public final class PostfixEvaluator { + private PostfixEvaluator() { + } + + private static final Set OPERATORS = Set.of("+", "-", "*", "/"); + + /** + * Evaluates the given postfix expression and returns the result. + * + * @param expression The postfix expression as a string with operands and operators separated by spaces. + * @return The result of evaluating the postfix expression. + * @throws IllegalArgumentException if the expression is invalid. + */ + public static int evaluatePostfix(String expression) { + Stack stack = new Stack<>(); + + for (String token : expression.split("\\s+")) { + if (isOperator(token)) { + int operand2 = stack.pop(); + int operand1 = stack.pop(); + stack.push(applyOperator(token, operand1, operand2)); + } else { + stack.push(Integer.valueOf(token)); + } + } + + if (stack.size() != 1) { + throw new IllegalArgumentException("Invalid expression"); + } + + return stack.pop(); + } + + /** + * Checks if the given token is an operator. + * + * @param token The token to check. + * @return true if the token is an operator, false otherwise. + */ + private static boolean isOperator(String token) { + return OPERATORS.contains(token); + } + + /** + * Applies the given operator to the two operands. + * + * @param operator The operator to apply. + * @param a The first operand. + * @param b The second operand. + * @return The result of applying the operator to the operands. + */ + private static int applyOperator(String operator, int a, int b) { + return switch (operator) { + case "+" -> a + b; + case "-" -> a - b; + case "*" -> a * b; + case "/" -> a / b; + default -> throw new IllegalArgumentException("Invalid operator"); + }; + } +} diff --git a/src/main/java/com/thealgorithms/stacks/PrefixEvaluator.java b/src/main/java/com/thealgorithms/stacks/PrefixEvaluator.java new file mode 100644 index 000000000000..eb2ce95e18d8 --- /dev/null +++ b/src/main/java/com/thealgorithms/stacks/PrefixEvaluator.java @@ -0,0 +1,76 @@ +package com.thealgorithms.stacks; + +import java.util.Set; +import java.util.Stack; + +/** + * Evaluate a prefix (Polish) expression using a stack. + * + *

Example: Expression "+ * 2 3 4" results in 10. + *

Applications: Useful for implementing compilers and interpreters. + * + * @author Hardvan + */ +public final class PrefixEvaluator { + private PrefixEvaluator() { + } + + private static final Set OPERATORS = Set.of("+", "-", "*", "/"); + + /** + * Evaluates the given prefix expression and returns the result. + * + * @param expression The prefix expression as a string with operands and operators separated by spaces. + * @return The result of evaluating the prefix expression. + * @throws IllegalArgumentException if the expression is invalid. + */ + public static int evaluatePrefix(String expression) { + Stack stack = new Stack<>(); + String[] tokens = expression.split("\\s+"); + + for (int i = tokens.length - 1; i >= 0; i--) { + String token = tokens[i]; + if (isOperator(token)) { + int operand1 = stack.pop(); + int operand2 = stack.pop(); + stack.push(applyOperator(token, operand1, operand2)); + } else { + stack.push(Integer.valueOf(token)); + } + } + + if (stack.size() != 1) { + throw new IllegalArgumentException("Invalid expression"); + } + + return stack.pop(); + } + + /** + * Checks if the given token is an operator. + * + * @param token The token to check. + * @return true if the token is an operator, false otherwise. + */ + private static boolean isOperator(String token) { + return OPERATORS.contains(token); + } + + /** + * Applies the given operator to the two operands. + * + * @param operator The operator to apply. + * @param a The first operand. + * @param b The second operand. + * @return The result of applying the operator to the operands. + */ + private static int applyOperator(String operator, int a, int b) { + return switch (operator) { + case "+" -> a + b; + case "-" -> a - b; + case "*" -> a * b; + case "/" -> a / b; + default -> throw new IllegalArgumentException("Invalid operator"); + }; + } +} diff --git a/src/main/java/com/thealgorithms/stacks/SmallestElementConstantTime.java b/src/main/java/com/thealgorithms/stacks/SmallestElementConstantTime.java new file mode 100644 index 000000000000..9864ef9b0f97 --- /dev/null +++ b/src/main/java/com/thealgorithms/stacks/SmallestElementConstantTime.java @@ -0,0 +1,74 @@ +package com.thealgorithms.stacks; + +import java.util.NoSuchElementException; +import java.util.Stack; + +/** + * A class that implements a stack that gives the minimum element in O(1) time. + * The mainStack is used to store the all the elements of the stack + * While the minStack stores the minimum elements + * When we want to get a minimum element, we call the top of the minimum stack + * + * Problem: https://www.baeldung.com/cs/stack-constant-time + */ +public class SmallestElementConstantTime { + private Stack mainStack; // initialize a mainStack + private Stack minStack; // initialize a minStack + + /** + * Constructs two empty stacks + */ + public SmallestElementConstantTime() { + mainStack = new Stack<>(); + minStack = new Stack<>(); + } + + /** + * Pushes an element onto the top of the stack. + * Checks if the element is the minimum or not + * If so, then pushes to the minimum stack + * @param data The element to be pushed onto the stack. + */ + public void push(int data) { + if (mainStack.isEmpty()) { + mainStack.push(data); + minStack.push(data); + return; + } + + mainStack.push(data); + if (data < minStack.peek()) { + minStack.push(data); + } + } + + /** + * Pops an element from the stack. + * Checks if the element to be popped is the minimum or not + * If so, then pop from the minStack + * + * @throws NoSuchElementException if the stack is empty. + */ + public void pop() { + if (mainStack.isEmpty()) { + throw new NoSuchElementException("Stack is empty"); + } + + int ele = mainStack.pop(); + if (ele == minStack.peek()) { + minStack.pop(); + } + } + + /** + * Returns the minimum element present in the stack + * + * @return The element at the top of the minStack, or null if the stack is empty. + */ + public Integer getMinimumElement() { + if (minStack.isEmpty()) { + return null; + } + return minStack.peek(); + } +} diff --git a/src/main/java/com/thealgorithms/stacks/SortStack.java b/src/main/java/com/thealgorithms/stacks/SortStack.java new file mode 100644 index 000000000000..d07d1a5f1dc3 --- /dev/null +++ b/src/main/java/com/thealgorithms/stacks/SortStack.java @@ -0,0 +1,60 @@ +package com.thealgorithms.stacks; + +import java.util.Stack; + +/** + * A utility class that provides a method to sort a stack using recursion. + * The elements are sorted in ascending order, with the largest element at the top. + * This algorithm is implemented using only recursion and the original stack, + * without utilizing any additional data structures apart from the stack itself. + */ +public final class SortStack { + private SortStack() { + } + + /** + * Sorts the given stack in ascending order using recursion. + * The sorting is performed such that the largest element ends up on top of the stack. + * This method modifies the original stack and does not return a new stack. + * + * The algorithm works as follows: + * 1. Remove the top element. + * 2. Recursively sort the remaining stack. + * 3. Insert the removed element back into the sorted stack at the correct position. + * + * @param stack The stack to be sorted, containing Integer elements. + * @throws IllegalArgumentException if the stack contains `null` elements. + */ + public static void sortStack(Stack stack) { + if (stack.isEmpty()) { + return; + } + + int top = stack.pop(); + sortStack(stack); + insertInSortedOrder(stack, top); + } + + /** + * Helper method to insert an element into the correct position in a sorted stack. + * This method is called recursively to place the given element into the stack + * such that the stack remains sorted in ascending order. + * + * The element is inserted in such a way that all elements below it are smaller + * (if the stack is non-empty), and elements above it are larger, maintaining + * the ascending order. + * + * @param stack The stack in which the element needs to be inserted. + * @param element The element to be inserted into the stack in sorted order. + */ + private static void insertInSortedOrder(Stack stack, int element) { + if (stack.isEmpty() || element > stack.peek()) { + stack.push(element); + return; + } + + int top = stack.pop(); + insertInSortedOrder(stack, element); + stack.push(top); + } +} diff --git a/src/main/java/com/thealgorithms/stacks/StackUsingTwoQueues.java b/src/main/java/com/thealgorithms/stacks/StackUsingTwoQueues.java new file mode 100644 index 000000000000..5b1ca5d1d5a5 --- /dev/null +++ b/src/main/java/com/thealgorithms/stacks/StackUsingTwoQueues.java @@ -0,0 +1,91 @@ +package com.thealgorithms.stacks; + +import java.util.LinkedList; +import java.util.NoSuchElementException; +import java.util.Queue; + +/** + * A class that implements a stack using two queues. + * This approach ensures that the stack's LIFO (Last In, First Out) behavior + * is maintained by utilizing two queues for storage. + * The mainQueue is used to store the elements of the stack, while the tempQueue + * is used to temporarily store elements during the push operation. + */ +public class StackUsingTwoQueues { + + private Queue mainQueue; + private Queue tempQueue; + + /** + * Constructs an empty stack using two queues. + */ + public StackUsingTwoQueues() { + mainQueue = new LinkedList<>(); + tempQueue = new LinkedList<>(); + } + + /** + * Pushes an element onto the top of the stack. + * The newly pushed element becomes the top of the stack. + * + * @param item The element to be pushed onto the stack. + */ + public void push(int item) { + tempQueue.add(item); + + // Move all elements from the mainQueue to tempQueue to maintain LIFO order + while (!mainQueue.isEmpty()) { + tempQueue.add(mainQueue.remove()); + } + + // Swap the names of the two queues + Queue swap = mainQueue; + mainQueue = tempQueue; + tempQueue = swap; // tempQueue is now empty + } + + /** + * Removes and returns the element at the top of the stack. + * Throws an exception if the stack is empty. + * + * @return The element at the top of the stack. + * @throws NoSuchElementException if the stack is empty. + */ + public int pop() { + if (mainQueue.isEmpty()) { + throw new NoSuchElementException("Stack is empty"); + } + return mainQueue.remove(); + } + + /** + * Returns the element at the top of the stack without removing it. + * Returns null if the stack is empty. + * + * @return The element at the top of the stack, or null if the stack is empty. + */ + public Integer peek() { + if (mainQueue.isEmpty()) { + return null; + } + return mainQueue.peek(); + } + + /** + * Returns true if the stack is empty. + * + * @return true if the stack is empty; false otherwise. + */ + public boolean isEmpty() { + return mainQueue.isEmpty(); + } + + /** + * Returns the number of elements in the stack. + * + * @return The size of the stack. + */ + public int size() { + return mainQueue.size(); + } +} diff --git a/src/main/java/com/thealgorithms/strings/LongestCommonPrefix.java b/src/main/java/com/thealgorithms/strings/LongestCommonPrefix.java new file mode 100644 index 000000000000..0fabdaa2658b --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/LongestCommonPrefix.java @@ -0,0 +1,22 @@ +package com.thealgorithms.strings; + +import java.util.Arrays; + +public final class LongestCommonPrefix { + public String longestCommonPrefix(String[] strs) { + if (strs == null || strs.length == 0) { + return ""; + } + + Arrays.sort(strs); + String shortest = strs[0]; + String longest = strs[strs.length - 1]; + + int index = 0; + while (index < shortest.length() && index < longest.length() && shortest.charAt(index) == longest.charAt(index)) { + index++; + } + + return shortest.substring(0, index); + } +} diff --git a/src/main/java/com/thealgorithms/strings/ReverseString.java b/src/main/java/com/thealgorithms/strings/ReverseString.java index 46a0494fcbb4..54a9b779e828 100644 --- a/src/main/java/com/thealgorithms/strings/ReverseString.java +++ b/src/main/java/com/thealgorithms/strings/ReverseString.java @@ -36,4 +36,25 @@ public static String reverse2(String str) { } return new String(value); } + + /** + * Reverse version 3 the given string using a StringBuilder. + * This method converts the string to a character array, + * iterates through it in reverse order, and appends each character + * to a StringBuilder. + * + * @param string The input string to be reversed. + * @return The reversed string. + */ + public static String reverse3(String string) { + if (string.isEmpty()) { + return string; + } + char[] chars = string.toCharArray(); + StringBuilder sb = new StringBuilder(); + for (int i = string.length() - 1; i >= 0; i--) { + sb.append(chars[i]); + } + return sb.toString(); + } } diff --git a/src/main/java/com/thealgorithms/strings/Upper.java b/src/main/java/com/thealgorithms/strings/Upper.java index fa9a408416ea..5e248cb6ee39 100644 --- a/src/main/java/com/thealgorithms/strings/Upper.java +++ b/src/main/java/com/thealgorithms/strings/Upper.java @@ -21,15 +21,19 @@ public static void main(String[] args) { * @return the {@code String}, converted to uppercase. */ public static String toUpperCase(String s) { - if (s == null || s.isEmpty()) { + if (s == null) { + throw new IllegalArgumentException("Input string connot be null"); + } + if (s.isEmpty()) { return s; } - char[] values = s.toCharArray(); - for (int i = 0; i < values.length; ++i) { - if (Character.isLetter(values[i]) && Character.isLowerCase(values[i])) { - values[i] = Character.toUpperCase(values[i]); + 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)); } } - return new String(values); + return result.toString(); } } diff --git a/src/main/java/com/thealgorithms/strings/ValidParentheses.java b/src/main/java/com/thealgorithms/strings/ValidParentheses.java index f4f3761b0495..629fee495d84 100644 --- a/src/main/java/com/thealgorithms/strings/ValidParentheses.java +++ b/src/main/java/com/thealgorithms/strings/ValidParentheses.java @@ -38,4 +38,22 @@ public static boolean isValid(String s) { } return head == 0; } + public static boolean isValidParentheses(String s) { + int i = -1; + char[] stack = new char[s.length()]; + String openBrackets = "({["; + String closeBrackets = ")}]"; + for (char ch : s.toCharArray()) { + if (openBrackets.indexOf(ch) != -1) { + stack[++i] = ch; + } else { + if (i >= 0 && openBrackets.indexOf(stack[i]) == closeBrackets.indexOf(ch)) { + i--; + } else { + return false; + } + } + } + return i == -1; + } } diff --git a/src/main/java/com/thealgorithms/strings/WordLadder.java b/src/main/java/com/thealgorithms/strings/WordLadder.java index 084a682b04a7..665e5ff3220d 100644 --- a/src/main/java/com/thealgorithms/strings/WordLadder.java +++ b/src/main/java/com/thealgorithms/strings/WordLadder.java @@ -1,8 +1,8 @@ package com.thealgorithms.strings; +import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; -import java.util.List; import java.util.Queue; import java.util.Set; @@ -22,7 +22,7 @@ private WordLadder() { * @param wordList a list of words that can be used in the transformation sequence * @return the number of words in the shortest transformation sequence, or 0 if no such sequence exists */ - public static int ladderLength(String beginWord, String endWord, List wordList) { + public static int ladderLength(String beginWord, String endWord, Collection wordList) { Set wordSet = new HashSet<>(wordList); if (!wordSet.contains(endWord)) { diff --git a/src/main/java/com/thealgorithms/strings/zigZagPattern/ZigZagPattern.java b/src/main/java/com/thealgorithms/strings/zigZagPattern/ZigZagPattern.java index 3f33fc17b9b0..ad7835bdbb97 100644 --- a/src/main/java/com/thealgorithms/strings/zigZagPattern/ZigZagPattern.java +++ b/src/main/java/com/thealgorithms/strings/zigZagPattern/ZigZagPattern.java @@ -1,41 +1,38 @@ package com.thealgorithms.strings.zigZagPattern; final class ZigZagPattern { + private ZigZagPattern() { } + /** + * Encodes a given string into a zig-zag pattern. + * + * @param s the input string to be encoded + * @param numRows the number of rows in the zigzag pattern + * @return the encoded string in zigzag pattern format + */ public static String encode(String s, int numRows) { if (numRows < 2 || s.length() < numRows) { return s; } - int start = 0; - int index = 0; - int height = 1; - int depth = numRows; - char[] zigZagedArray = new char[s.length()]; - while (depth != 0) { - int pointer = start; - int heightSpace = 2 + ((height - 2) * 2); - int depthSpace = 2 + ((depth - 2) * 2); - boolean bool = true; - while (pointer < s.length()) { - zigZagedArray[index++] = s.charAt(pointer); - if (heightSpace == 0) { - pointer += depthSpace; - } else if (depthSpace == 0) { - pointer += heightSpace; - } else if (bool) { - pointer += depthSpace; - bool = false; - } else { - pointer += heightSpace; - bool = true; + + StringBuilder result = new StringBuilder(s.length()); + int cycleLength = 2 * numRows - 2; + + for (int row = 0; row < numRows; row++) { + for (int j = row; j < s.length(); j += cycleLength) { + result.append(s.charAt(j)); + + if (row > 0 && row < numRows - 1) { + int diagonal = j + cycleLength - 2 * row; + if (diagonal < s.length()) { + result.append(s.charAt(diagonal)); + } } } - height++; - depth--; - start++; } - return new String(zigZagedArray); + + return result.toString(); } } diff --git a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java new file mode 100644 index 000000000000..236a23205180 --- /dev/null +++ b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java @@ -0,0 +1,157 @@ +package com.thealgorithms.tree; + +import java.util.ArrayList; +import java.util.List; + +/** + * Heavy-Light Decomposition (HLD) implementation in Java. + * HLD is used to efficiently handle path queries on trees, such as maximum, + * sum, or updates. It decomposes the tree into heavy and light chains, + * enabling queries in O(log N) time. + * Wikipedia Reference: https://en.wikipedia.org/wiki/Heavy-light_decomposition + * Author: Nithin U. + * Github: https://github.com/NithinU2802 + */ + +public class HeavyLightDecomposition { + private List> tree; + private int[] parent; + private int[] depth; + private int[] subtreeSize; + private int[] chainHead; + private int[] position; + private int[] nodeValue; + private int[] segmentTree; + private int positionIndex; + + public HeavyLightDecomposition(int n) { + tree = new ArrayList<>(); + for (int i = 0; i <= n; i++) { + tree.add(new ArrayList<>()); + } + parent = new int[n + 1]; + depth = new int[n + 1]; + subtreeSize = new int[n + 1]; + chainHead = new int[n + 1]; + position = new int[n + 1]; + nodeValue = new int[n + 1]; + segmentTree = new int[4 * (n + 1)]; + for (int i = 0; i <= n; i++) { + chainHead[i] = -1; + } + positionIndex = 0; + } + + public int getPosition(int index) { + return position[index]; + } + + public int getPositionIndex() { + return positionIndex; + } + + public void addEdge(int u, int v) { + tree.get(u).add(v); + tree.get(v).add(u); + } + + private void dfsSize(int node, int parentNode) { + parent[node] = parentNode; + subtreeSize[node] = 1; + for (int child : tree.get(node)) { + if (child != parentNode) { + depth[child] = depth[node] + 1; + dfsSize(child, node); + subtreeSize[node] += subtreeSize[child]; + } + } + } + + private void decompose(int node, int head) { + chainHead[node] = head; + position[node] = positionIndex++; + int heavyChild = -1; + int maxSubtreeSize = -1; + for (int child : tree.get(node)) { + if (child != parent[node] && subtreeSize[child] > maxSubtreeSize) { + heavyChild = child; + maxSubtreeSize = subtreeSize[child]; + } + } + if (heavyChild != -1) { + decompose(heavyChild, head); + } + for (int child : tree.get(node)) { + if (child != parent[node] && child != heavyChild) { + decompose(child, child); + } + } + } + + private void buildSegmentTree(int node, int start, int end) { + if (start == end) { + segmentTree[node] = nodeValue[start]; + return; + } + int mid = (start + end) / 2; + buildSegmentTree(2 * node, start, mid); + buildSegmentTree(2 * node + 1, mid + 1, end); + segmentTree[node] = Math.max(segmentTree[2 * node], segmentTree[2 * node + 1]); + } + + public void updateSegmentTree(int node, int start, int end, int index, int value) { + if (start == end) { + segmentTree[node] = value; + return; + } + int mid = (start + end) / 2; + if (index <= mid) { + updateSegmentTree(2 * node, start, mid, index, value); + } else { + updateSegmentTree(2 * node + 1, mid + 1, end, index, value); + } + segmentTree[node] = Math.max(segmentTree[2 * node], segmentTree[2 * node + 1]); + } + + public int querySegmentTree(int node, int start, int end, int left, int right) { + if (left > end || right < start) { + return Integer.MIN_VALUE; + } + if (left <= start && end <= right) { + return segmentTree[node]; + } + int mid = (start + end) / 2; + int leftQuery = querySegmentTree(2 * node, start, mid, left, right); + int rightQuery = querySegmentTree(2 * node + 1, mid + 1, end, left, right); + return Math.max(leftQuery, rightQuery); + } + + public int queryMaxInPath(int u, int v) { + int result = Integer.MIN_VALUE; + while (chainHead[u] != chainHead[v]) { + if (depth[chainHead[u]] < depth[chainHead[v]]) { + int temp = u; + u = v; + v = temp; + } + result = Math.max(result, querySegmentTree(1, 0, positionIndex - 1, position[chainHead[u]], position[u])); + u = parent[chainHead[u]]; + } + if (depth[u] > depth[v]) { + int temp = u; + u = v; + v = temp; + } + result = Math.max(result, querySegmentTree(1, 0, positionIndex - 1, position[u], position[v])); + return result; + } + + public void initialize(int root, int[] values) { + dfsSize(root, -1); + decompose(root, root); + for (int i = 0; i < values.length; i++) { + nodeValue[position[i]] = values[i]; + } + buildSegmentTree(1, 0, positionIndex - 1); + } +} diff --git a/src/test/java/com/thealgorithms/audiofilters/EMAFilterTest.java b/src/test/java/com/thealgorithms/audiofilters/EMAFilterTest.java new file mode 100644 index 000000000000..f2338d3d8296 --- /dev/null +++ b/src/test/java/com/thealgorithms/audiofilters/EMAFilterTest.java @@ -0,0 +1,41 @@ +package com.thealgorithms.audiofilters; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.Test; + +public class EMAFilterTest { + + @Test + public void testApplyBasicSignal() { + EMAFilter emaFilter = new EMAFilter(0.2); + double[] audioSignal = {0.1, 0.5, 0.8, 0.6, 0.3, 0.9, 0.4}; + double[] expectedOutput = {0.1, 0.18, 0.304, 0.3632, 0.35056, 0.460448, 0.4483584}; + double[] result = emaFilter.apply(audioSignal); + assertArrayEquals(expectedOutput, result, 1e-5); + } + + @Test + public void testApplyEmptySignal() { + EMAFilter emaFilter = new EMAFilter(0.2); + double[] audioSignal = {}; + double[] expectedOutput = {}; + double[] result = emaFilter.apply(audioSignal); + assertArrayEquals(expectedOutput, result); + } + + @Test + public void testAlphaBounds() { + EMAFilter emaFilterMin = new EMAFilter(0.01); + EMAFilter emaFilterMax = new EMAFilter(1.0); + double[] audioSignal = {1.0, 1.0, 1.0, 1.0}; + + // Minimal smoothing (alpha close to 0) + double[] resultMin = emaFilterMin.apply(audioSignal); + assertArrayEquals(audioSignal, resultMin, 1e-5); + + // Maximum smoothing (alpha = 1, output should match input) + double[] resultMax = emaFilterMax.apply(audioSignal); + assertArrayEquals(audioSignal, resultMax, 1e-5); + } +} diff --git a/src/test/java/com/thealgorithms/backtracking/ArrayCombinationTest.java b/src/test/java/com/thealgorithms/backtracking/ArrayCombinationTest.java index a4ff7fe892d5..a6a3714cb594 100644 --- a/src/test/java/com/thealgorithms/backtracking/ArrayCombinationTest.java +++ b/src/test/java/com/thealgorithms/backtracking/ArrayCombinationTest.java @@ -27,10 +27,14 @@ void testCombinationThrows(int n, int k) { private static Stream regularInputs() { return Stream.of(Arguments.of(0, 0, List.of(new ArrayList())), Arguments.of(1, 0, List.of(new ArrayList())), Arguments.of(1, 1, List.of(List.of(0))), Arguments.of(3, 0, List.of(new ArrayList())), Arguments.of(3, 1, List.of(List.of(0), List.of(1), List.of(2))), - Arguments.of(4, 2, List.of(List.of(0, 1), List.of(0, 2), List.of(0, 3), List.of(1, 2), List.of(1, 3), List.of(2, 3)))); + Arguments.of(4, 2, List.of(List.of(0, 1), List.of(0, 2), List.of(0, 3), List.of(1, 2), List.of(1, 3), List.of(2, 3))), + Arguments.of(5, 3, List.of(List.of(0, 1, 2), List.of(0, 1, 3), List.of(0, 1, 4), List.of(0, 2, 3), List.of(0, 2, 4), List.of(0, 3, 4), List.of(1, 2, 3), List.of(1, 2, 4), List.of(1, 3, 4), List.of(2, 3, 4))), + Arguments.of(6, 4, + List.of(List.of(0, 1, 2, 3), List.of(0, 1, 2, 4), List.of(0, 1, 2, 5), List.of(0, 1, 3, 4), List.of(0, 1, 3, 5), List.of(0, 1, 4, 5), List.of(0, 2, 3, 4), List.of(0, 2, 3, 5), List.of(0, 2, 4, 5), List.of(0, 3, 4, 5), List.of(1, 2, 3, 4), List.of(1, 2, 3, 5), List.of(1, 2, 4, 5), + List.of(1, 3, 4, 5), List.of(2, 3, 4, 5)))); } private static Stream wrongInputs() { - return Stream.of(Arguments.of(-1, 0), Arguments.of(0, -1), Arguments.of(2, 100)); + return Stream.of(Arguments.of(-1, 0), Arguments.of(0, -1), Arguments.of(2, 100), Arguments.of(3, 4)); } } diff --git a/src/test/java/com/thealgorithms/backtracking/CombinationTest.java b/src/test/java/com/thealgorithms/backtracking/CombinationTest.java index 44edc3077fd5..a9d1163f3ecd 100644 --- a/src/test/java/com/thealgorithms/backtracking/CombinationTest.java +++ b/src/test/java/com/thealgorithms/backtracking/CombinationTest.java @@ -1,17 +1,28 @@ package com.thealgorithms.backtracking; +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.List; +import java.util.Set; import java.util.TreeSet; import org.junit.jupiter.api.Test; public class CombinationTest { + @Test + void testNegativeElement() { + Integer[] array = {1, 2}; + assertThrows(IllegalArgumentException.class, () -> { Combination.combination(array, -1); }); + } + @Test void testNoElement() { List> result = Combination.combination(new Integer[] {1, 2}, 0); - assertTrue(result == null); + assertNotNull(result); + assertEquals(0, result.size()); } @Test @@ -28,4 +39,13 @@ void testLengthTwo() { assertTrue(arr[0] == 1); assertTrue(arr[1] == 2); } + + @Test + void testCombinationsWithStrings() { + List> result = Combination.combination(new String[] {"a", "b", "c"}, 2); + assertEquals(3, result.size()); + assertTrue(result.contains(new TreeSet<>(Set.of("a", "b")))); + assertTrue(result.contains(new TreeSet<>(Set.of("a", "c")))); + assertTrue(result.contains(new TreeSet<>(Set.of("b", "c")))); + } } diff --git a/src/test/java/com/thealgorithms/backtracking/CrosswordSolverTest.java b/src/test/java/com/thealgorithms/backtracking/CrosswordSolverTest.java new file mode 100644 index 000000000000..542fd53fe5ed --- /dev/null +++ b/src/test/java/com/thealgorithms/backtracking/CrosswordSolverTest.java @@ -0,0 +1,66 @@ +package com.thealgorithms.backtracking; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class CrosswordSolverTest { + + @Test + public void testValidPlacement() { + char[][] puzzle = {{' ', ' ', ' '}, {' ', ' ', ' '}, {' ', ' ', ' '}}; + assertTrue(CrosswordSolver.isValid(puzzle, "cat", 0, 0, true)); + assertTrue(CrosswordSolver.isValid(puzzle, "dog", 0, 0, false)); + assertFalse(CrosswordSolver.isValid(puzzle, "cat", 1, 2, false)); + } + + @Test + public void testPlaceAndRemoveWord() { + char[][] puzzle = {{' ', ' ', ' '}, {' ', ' ', ' '}, {' ', ' ', ' '}}; + CrosswordSolver.placeWord(puzzle, "cat", 0, 0, true); + assertEquals('c', puzzle[0][0]); + assertEquals('a', puzzle[1][0]); + assertEquals('t', puzzle[2][0]); + + CrosswordSolver.removeWord(puzzle, "cat", 0, 0, true); + assertEquals(' ', puzzle[0][0]); + assertEquals(' ', puzzle[1][0]); + assertEquals(' ', puzzle[2][0]); + } + + @Test + public void testSolveCrossword() { + char[][] puzzle = {{' ', ' ', ' '}, {' ', ' ', ' '}, {' ', ' ', ' '}}; + List words = Arrays.asList("cat", "dog", "car"); + assertTrue(CrosswordSolver.solveCrossword(puzzle, words)); + + /* Solved crossword: + * c d c + * a o a + * t g r + */ + + assertEquals('c', puzzle[0][0]); + assertEquals('a', puzzle[1][0]); + assertEquals('t', puzzle[2][0]); + + assertEquals('d', puzzle[0][1]); + assertEquals('o', puzzle[1][1]); + assertEquals('g', puzzle[2][1]); + + assertEquals('c', puzzle[0][2]); + assertEquals('a', puzzle[1][2]); + assertEquals('r', puzzle[2][2]); + } + + @Test + public void testNoSolution() { + char[][] puzzle = {{' ', ' ', ' '}, {' ', ' ', ' '}, {' ', ' ', ' '}}; + List words = Arrays.asList("cat", "dog", "elephant"); // 'elephant' is too long for the grid + assertFalse(CrosswordSolver.solveCrossword(puzzle, words)); + } +} diff --git a/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java b/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java index edaca14af067..b8e77fb38bad 100644 --- a/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java +++ b/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java @@ -11,41 +11,35 @@ public class MazeRecursionTest { @Test - public void testMaze() { - // First create a 2 dimensions array to mimic a maze map + public void testSolveMazeUsingFirstAndSecondStrategy() { int[][] map = new int[8][7]; int[][] map2 = new int[8][7]; - // We use 1 to indicate wall + // We use 1 to indicate walls // Set the ceiling and floor to 1 for (int i = 0; i < 7; i++) { map[0][i] = 1; map[7][i] = 1; } - - // Then we set the left and right wall to 1 + // Set the left and right wall to 1 for (int i = 0; i < 8; i++) { map[i][0] = 1; map[i][6] = 1; } - - // Now we have created a maze with its wall initialized - - // Here we set the obstacle + // Set obstacles map[3][1] = 1; map[3][2] = 1; - // clone another map for setWay2 method + // Clone the original map for the second pathfinding strategy for (int i = 0; i < map.length; i++) { - for (int j = 0; j < map[i].length; j++) { - map2[i][j] = map[i][j]; - } + System.arraycopy(map[i], 0, map2[i], 0, map[i].length); } - MazeRecursion.setWay(map, 1, 1); - MazeRecursion.setWay2(map2, 1, 1); - - int[][] expectedMap = new int[][] { + // Solve the maze using the first strategy + int[][] solvedMap1 = MazeRecursion.solveMazeUsingFirstStrategy(map); + // Solve the maze using the second strategy + int[][] solvedMap2 = MazeRecursion.solveMazeUsingSecondStrategy(map2); + int[][] expectedMap1 = new int[][] { {1, 1, 1, 1, 1, 1, 1}, {1, 2, 0, 0, 0, 0, 1}, {1, 2, 2, 2, 0, 0, 1}, @@ -55,7 +49,6 @@ public void testMaze() { {1, 0, 0, 2, 2, 2, 1}, {1, 1, 1, 1, 1, 1, 1}, }; - int[][] expectedMap2 = new int[][] { {1, 1, 1, 1, 1, 1, 1}, {1, 2, 2, 2, 2, 2, 1}, @@ -67,7 +60,8 @@ public void testMaze() { {1, 1, 1, 1, 1, 1, 1}, }; - assertArrayEquals(map, expectedMap); - assertArrayEquals(map2, expectedMap2); + // Assert the results + assertArrayEquals(expectedMap1, solvedMap1); + assertArrayEquals(expectedMap2, solvedMap2); } } diff --git a/src/test/java/com/thealgorithms/backtracking/NQueensTest.java b/src/test/java/com/thealgorithms/backtracking/NQueensTest.java index 977e3dfae2ce..243133848ee2 100644 --- a/src/test/java/com/thealgorithms/backtracking/NQueensTest.java +++ b/src/test/java/com/thealgorithms/backtracking/NQueensTest.java @@ -1,5 +1,6 @@ package com.thealgorithms.backtracking; +import static java.util.Collections.singletonList; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.ArrayList; @@ -11,7 +12,7 @@ public class NQueensTest { @Test public void testNQueens1() { - List> expected = Arrays.asList(Arrays.asList("Q")); + List> expected = singletonList(singletonList("Q")); assertEquals(expected, NQueens.getNQueensArrangements(1)); } diff --git a/src/test/java/com/thealgorithms/backtracking/WordPatternMatcherTest.java b/src/test/java/com/thealgorithms/backtracking/WordPatternMatcherTest.java new file mode 100644 index 000000000000..4d56be566035 --- /dev/null +++ b/src/test/java/com/thealgorithms/backtracking/WordPatternMatcherTest.java @@ -0,0 +1,40 @@ +package com.thealgorithms.backtracking; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class WordPatternMatcherTest { + + @Test + public void testPatternMatchingSuccess() { + assertTrue(WordPatternMatcher.matchWordPattern("aba", "GraphTreesGraph")); + assertTrue(WordPatternMatcher.matchWordPattern("xyx", "PythonRubyPython")); + } + + @Test + public void testPatternMatchingFailure() { + assertFalse(WordPatternMatcher.matchWordPattern("GG", "PythonJavaPython")); + } + + @Test + public void testEmptyPatternAndString() { + assertTrue(WordPatternMatcher.matchWordPattern("", "")); + } + + @Test + public void testEmptyPattern() { + assertFalse(WordPatternMatcher.matchWordPattern("", "nonempty")); + } + + @Test + public void testEmptyString() { + assertFalse(WordPatternMatcher.matchWordPattern("abc", "")); + } + + @Test + public void testLongerPatternThanString() { + assertFalse(WordPatternMatcher.matchWordPattern("abcd", "abc")); + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/BcdConversionTest.java b/src/test/java/com/thealgorithms/bitmanipulation/BcdConversionTest.java new file mode 100644 index 000000000000..727b07eb9065 --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/BcdConversionTest.java @@ -0,0 +1,85 @@ +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 BcdConversionTest { + + @Test + public void testBcdToDecimal() { + int decimal = BcdConversion.bcdToDecimal(0x1234); + assertEquals(1234, decimal); // BCD 0x1234 should convert to decimal 1234 + } + + @Test + public void testDecimalToBcd() { + int bcd = BcdConversion.decimalToBcd(1234); + assertEquals(0x1234, bcd); // Decimal 1234 should convert to BCD 0x1234 + } + + @Test + public void testBcdToDecimalZero() { + int decimal = BcdConversion.bcdToDecimal(0x0); + assertEquals(0, decimal); // BCD 0x0 should convert to decimal 0 + } + + @Test + public void testDecimalToBcdZero() { + int bcd = BcdConversion.decimalToBcd(0); + assertEquals(0x0, bcd); // Decimal 0 should convert to BCD 0x0 + } + + @Test + public void testBcdToDecimalSingleDigit() { + int decimal = BcdConversion.bcdToDecimal(0x7); + assertEquals(7, decimal); // BCD 0x7 should convert to decimal 7 + } + + @Test + public void testDecimalToBcdSingleDigit() { + int bcd = BcdConversion.decimalToBcd(7); + assertEquals(0x7, bcd); // Decimal 7 should convert to BCD 0x7 + } + + @Test + public void testBcdToDecimalMaxValue() { + int decimal = BcdConversion.bcdToDecimal(0x9999); + assertEquals(9999, decimal); // BCD 0x9999 should convert to decimal 9999 + } + + @Test + public void testDecimalToBcdMaxValue() { + int bcd = BcdConversion.decimalToBcd(9999); + assertEquals(0x9999, bcd); // Decimal 9999 should convert to BCD 0x9999 + } + + @Test + public void testBcdToDecimalInvalidHighDigit() { + // Testing invalid BCD input where one of the digits is > 9 + assertThrows(IllegalArgumentException.class, () -> { + BcdConversion.bcdToDecimal(0x123A); // Invalid BCD, 'A' is not a valid digit + }); + } + + @Test + public void testDecimalToBcdInvalidValue() { + // Testing conversion for numbers greater than 9999, which cannot be represented in BCD + assertThrows(IllegalArgumentException.class, () -> { + BcdConversion.decimalToBcd(10000); // 10000 is too large for BCD representation + }); + } + + @Test + public void testBcdToDecimalLeadingZeroes() { + int decimal = BcdConversion.bcdToDecimal(0x0234); + assertEquals(234, decimal); // BCD 0x0234 should convert to decimal 234, ignoring leading zero + } + + @Test + public void testDecimalToBcdLeadingZeroes() { + int bcd = BcdConversion.decimalToBcd(234); + assertEquals(0x0234, bcd); // Decimal 234 should convert to BCD 0x0234 + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheckTest.java b/src/test/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheckTest.java new file mode 100644 index 000000000000..ff41344266e4 --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheckTest.java @@ -0,0 +1,18 @@ +package com.thealgorithms.bitmanipulation; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class BinaryPalindromeCheckTest { + + @Test + public void testIsBinaryPalindrome() { + assertTrue(BinaryPalindromeCheck.isBinaryPalindrome(9)); // 1001 is a palindrome + assertFalse(BinaryPalindromeCheck.isBinaryPalindrome(10)); // 1010 is not a palindrome + assertTrue(BinaryPalindromeCheck.isBinaryPalindrome(0)); // 0 is a palindrome + assertTrue(BinaryPalindromeCheck.isBinaryPalindrome(1)); // 1 is a palindrome + assertFalse(BinaryPalindromeCheck.isBinaryPalindrome(12)); // 1100 is not a palindrome + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/BooleanAlgebraGatesTest.java b/src/test/java/com/thealgorithms/bitmanipulation/BooleanAlgebraGatesTest.java new file mode 100644 index 000000000000..8737d05ec459 --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/BooleanAlgebraGatesTest.java @@ -0,0 +1,101 @@ +package com.thealgorithms.bitmanipulation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.thealgorithms.bitmanipulation.BooleanAlgebraGates.ANDGate; +import com.thealgorithms.bitmanipulation.BooleanAlgebraGates.BooleanGate; +import com.thealgorithms.bitmanipulation.BooleanAlgebraGates.NANDGate; +import com.thealgorithms.bitmanipulation.BooleanAlgebraGates.NORGate; +import com.thealgorithms.bitmanipulation.BooleanAlgebraGates.NOTGate; +import com.thealgorithms.bitmanipulation.BooleanAlgebraGates.ORGate; +import com.thealgorithms.bitmanipulation.BooleanAlgebraGates.XORGate; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; + +class BooleanAlgebraGatesTest { + + @ParameterizedTest(name = "ANDGate Test Case {index}: inputs={0} -> expected={1}") + @MethodSource("provideAndGateTestCases") + void testANDGate(List inputs, boolean expected) { + BooleanGate gate = new ANDGate(); + assertEquals(expected, gate.evaluate(inputs)); + } + + @ParameterizedTest(name = "ORGate Test Case {index}: inputs={0} -> expected={1}") + @MethodSource("provideOrGateTestCases") + void testORGate(List inputs, boolean expected) { + BooleanGate gate = new ORGate(); + assertEquals(expected, gate.evaluate(inputs)); + } + + @ParameterizedTest(name = "NOTGate Test Case {index}: input={0} -> expected={1}") + @CsvSource({"true, false", "false, true"}) + void testNOTGate(boolean input, boolean expected) { + NOTGate gate = new NOTGate(); + assertEquals(expected, gate.evaluate(input)); + } + + @ParameterizedTest(name = "XORGate Test Case {index}: inputs={0} -> expected={1}") + @MethodSource("provideXorGateTestCases") + void testXORGate(List inputs, boolean expected) { + BooleanGate gate = new XORGate(); + assertEquals(expected, gate.evaluate(inputs)); + } + + @ParameterizedTest(name = "NANDGate Test Case {index}: inputs={0} -> expected={1}") + @MethodSource("provideNandGateTestCases") + void testNANDGate(List inputs, boolean expected) { + BooleanGate gate = new NANDGate(); + assertEquals(expected, gate.evaluate(inputs)); + } + + @ParameterizedTest(name = "NORGate Test Case {index}: inputs={0} -> expected={1}") + @MethodSource("provideNorGateTestCases") + void testNORGate(List inputs, boolean expected) { + BooleanGate gate = new NORGate(); + assertEquals(expected, gate.evaluate(inputs)); + } + + // Helper methods to provide test data for each gate + + static Stream provideAndGateTestCases() { + return Stream.of(new Object[] {Arrays.asList(Boolean.TRUE, Boolean.TRUE, Boolean.TRUE), Boolean.TRUE}, new Object[] {Arrays.asList(Boolean.TRUE, Boolean.FALSE, Boolean.TRUE), Boolean.FALSE}, new Object[] {Arrays.asList(Boolean.FALSE, Boolean.FALSE, Boolean.FALSE), Boolean.FALSE}, + new Object[] {Collections.emptyList(), Boolean.TRUE} // AND over no inputs is true + ); + } + + static Stream provideOrGateTestCases() { + return Stream.of(new Object[] {Arrays.asList(Boolean.TRUE, Boolean.FALSE, Boolean.FALSE), Boolean.TRUE}, new Object[] {Arrays.asList(Boolean.FALSE, Boolean.FALSE, Boolean.FALSE), Boolean.FALSE}, new Object[] {Arrays.asList(Boolean.TRUE, Boolean.TRUE, Boolean.TRUE), Boolean.TRUE}, + new Object[] {Collections.emptyList(), Boolean.FALSE} // OR over no inputs is false + ); + } + + static Stream provideXorGateTestCases() { + return Stream.of(new Object[] {Arrays.asList(Boolean.TRUE, Boolean.FALSE, Boolean.TRUE), Boolean.FALSE}, // XOR over odd true + new Object[] {Arrays.asList(Boolean.TRUE, Boolean.FALSE, Boolean.FALSE), Boolean.TRUE}, // XOR over single true + new Object[] {Arrays.asList(Boolean.FALSE, Boolean.FALSE, Boolean.FALSE), Boolean.FALSE}, // XOR over all false + new Object[] {Arrays.asList(Boolean.TRUE, Boolean.TRUE), Boolean.FALSE} // XOR over even true + ); + } + + static Stream provideNandGateTestCases() { + return Stream.of(new Object[] {Arrays.asList(Boolean.TRUE, Boolean.TRUE, Boolean.TRUE), Boolean.FALSE}, // NAND of all true is false + new Object[] {Arrays.asList(Boolean.TRUE, Boolean.FALSE), Boolean.TRUE}, // NAND with one false is true + new Object[] {Arrays.asList(Boolean.FALSE, Boolean.FALSE), Boolean.TRUE}, // NAND of all false is true + new Object[] {Collections.emptyList(), Boolean.FALSE} // NAND over no inputs is false (negation of AND) + ); + } + + static Stream provideNorGateTestCases() { + return Stream.of(new Object[] {Arrays.asList(Boolean.FALSE, Boolean.FALSE), Boolean.TRUE}, // NOR of all false is true + new Object[] {Arrays.asList(Boolean.FALSE, Boolean.TRUE), Boolean.FALSE}, // NOR with one true is false + new Object[] {Arrays.asList(Boolean.TRUE, Boolean.TRUE), Boolean.FALSE}, // NOR of all true is false + new Object[] {Collections.emptyList(), Boolean.TRUE} // NOR over no inputs is true (negation of OR) + ); + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/ClearLeftmostSetBitTest.java b/src/test/java/com/thealgorithms/bitmanipulation/ClearLeftmostSetBitTest.java new file mode 100644 index 000000000000..e77889fb7b0a --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/ClearLeftmostSetBitTest.java @@ -0,0 +1,16 @@ +package com.thealgorithms.bitmanipulation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class ClearLeftmostSetBitTest { + + @Test + public void testClearLeftmostSetBit() { + assertEquals(10, ClearLeftmostSetBit.clearLeftmostSetBit(26)); // 11010 -> 01010 + assertEquals(0, ClearLeftmostSetBit.clearLeftmostSetBit(1)); // 1 -> 0 + assertEquals(3, ClearLeftmostSetBit.clearLeftmostSetBit(7)); // 111 -> 011 + assertEquals(2, ClearLeftmostSetBit.clearLeftmostSetBit(6)); // 0110 -> 0010 + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/CountLeadingZerosTest.java b/src/test/java/com/thealgorithms/bitmanipulation/CountLeadingZerosTest.java new file mode 100644 index 000000000000..6ab15fd2ab5a --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/CountLeadingZerosTest.java @@ -0,0 +1,16 @@ +package com.thealgorithms.bitmanipulation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class CountLeadingZerosTest { + + @Test + public void testCountLeadingZeros() { + assertEquals(29, CountLeadingZeros.countLeadingZeros(5)); // 000...0101 has 29 leading zeros + assertEquals(32, CountLeadingZeros.countLeadingZeros(0)); // 000...0000 has 32 leading zeros + assertEquals(31, CountLeadingZeros.countLeadingZeros(1)); // 000...0001 has 31 leading zeros + assertEquals(0, CountLeadingZeros.countLeadingZeros(-1)); // No leading zeros in negative number (-1) + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/FindNthBitTest.java b/src/test/java/com/thealgorithms/bitmanipulation/FindNthBitTest.java new file mode 100644 index 000000000000..978003d21358 --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/FindNthBitTest.java @@ -0,0 +1,39 @@ +package com.thealgorithms.bitmanipulation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public final class FindNthBitTest { + + /** + * A parameterized test that checks the value of the Nth bit for different inputs. + * + * @param num the number whose Nth bit is being tested + * @param n the bit position + * @param expected the expected value of the Nth bit (0 or 1) + */ + @ParameterizedTest + @MethodSource("provideTestCases") + void findNthBitParameterizedTest(int num, int n, int expected) { + assertEquals(expected, FindNthBit.findNthBit(num, n)); + } + + /** + * Provides the test cases as a stream of arguments for the parameterized test. + * + * @return a stream of test cases where each case consists of a number, the bit position, + * and the expected result. + */ + private static Stream provideTestCases() { + return Stream.of(Arguments.of(13, 2, 0), // binary: 1101, 2nd bit is 0 + Arguments.of(13, 3, 1), // binary: 1101, 3rd bit is 1 + Arguments.of(4, 2, 0), // binary: 100, 2nd bit is 0 + Arguments.of(4, 3, 1), // binary: 100, 3rd bit is 1 + Arguments.of(1, 1, 1) // binary: 1, 1st bit is 1 + ); + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/FirstDifferentBitTest.java b/src/test/java/com/thealgorithms/bitmanipulation/FirstDifferentBitTest.java new file mode 100644 index 000000000000..ae7bad9a666a --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/FirstDifferentBitTest.java @@ -0,0 +1,15 @@ +package com.thealgorithms.bitmanipulation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +public class FirstDifferentBitTest { + + @ParameterizedTest + @CsvSource({"10, 8, 1", "7, 5, 1", "15, 14, 0", "1, 2, 0"}) + void testFirstDifferentBit(int x, int y, int expected) { + assertEquals(expected, FirstDifferentBit.firstDifferentBit(x, y)); + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/GenerateSubsetsTest.java b/src/test/java/com/thealgorithms/bitmanipulation/GenerateSubsetsTest.java new file mode 100644 index 000000000000..912d7e729ade --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/GenerateSubsetsTest.java @@ -0,0 +1,53 @@ +package com.thealgorithms.bitmanipulation; + +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +class GenerateSubsetsTest { + + @Test + void testGenerateSubsetsWithTwoElements() { + int[] set = {1, 2}; + List> expected = new ArrayList<>(); + expected.add(new ArrayList<>()); + expected.add(singletonList(1)); + expected.add(singletonList(2)); + expected.add(Arrays.asList(1, 2)); + + List> result = GenerateSubsets.generateSubsets(set); + assertEquals(expected, result); + } + + @Test + void testGenerateSubsetsWithOneElement() { + int[] set = {3}; + List> expected = new ArrayList<>(); + expected.add(new ArrayList<>()); + expected.add(singletonList(3)); + + List> result = GenerateSubsets.generateSubsets(set); + assertEquals(expected, result); + } + + @Test + void testGenerateSubsetsWithThreeElements() { + int[] set = {4, 5, 6}; + List> expected = new ArrayList<>(); + expected.add(new ArrayList<>()); + expected.add(singletonList(4)); + expected.add(singletonList(5)); + expected.add(Arrays.asList(4, 5)); + expected.add(singletonList(6)); + expected.add(Arrays.asList(4, 6)); + expected.add(Arrays.asList(5, 6)); + expected.add(Arrays.asList(4, 5, 6)); + + List> result = GenerateSubsets.generateSubsets(set); + assertEquals(expected, result); + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/GrayCodeConversionTest.java b/src/test/java/com/thealgorithms/bitmanipulation/GrayCodeConversionTest.java new file mode 100644 index 000000000000..1fe792028dca --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/GrayCodeConversionTest.java @@ -0,0 +1,29 @@ +package com.thealgorithms.bitmanipulation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class GrayCodeConversionTest { + + @Test + public void testBinaryToGray() { + assertEquals(7, GrayCodeConversion.binaryToGray(5)); // 101 -> 111 + assertEquals(4, GrayCodeConversion.binaryToGray(7)); // 111 -> 100 + assertEquals(1, GrayCodeConversion.binaryToGray(1)); // 001 -> 001 + } + + @Test + public void testGrayToBinary() { + assertEquals(5, GrayCodeConversion.grayToBinary(7)); // 111 -> 101 + assertEquals(4, GrayCodeConversion.grayToBinary(6)); // 110 -> 100 + assertEquals(1, GrayCodeConversion.grayToBinary(1)); // 001 -> 001 + } + + @Test + public void testBinaryGrayCycle() { + int binary = 9; // 1001 in binary + int gray = GrayCodeConversion.binaryToGray(binary); + assertEquals(binary, GrayCodeConversion.grayToBinary(gray)); // Should return to original binary + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/HammingDistanceTest.java b/src/test/java/com/thealgorithms/bitmanipulation/HammingDistanceTest.java new file mode 100644 index 000000000000..bde39a69f190 --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/HammingDistanceTest.java @@ -0,0 +1,17 @@ +package com.thealgorithms.bitmanipulation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class HammingDistanceTest { + + @Test + public void testHammingDistance() { + assertEquals(3, HammingDistance.hammingDistance(9, 14)); // 1001 vs 1110, Hamming distance is 3 + assertEquals(0, HammingDistance.hammingDistance(10, 10)); // Same number, Hamming distance is 0 + assertEquals(1, HammingDistance.hammingDistance(1, 0)); // 0001 vs 0000, Hamming distance is 1 + assertEquals(2, HammingDistance.hammingDistance(4, 1)); // 100 vs 001, Hamming distance is 2 + assertEquals(4, HammingDistance.hammingDistance(0, 15)); // 0000 vs 1111, Hamming distance is 4 + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/HigherLowerPowerOfTwoTest.java b/src/test/java/com/thealgorithms/bitmanipulation/HigherLowerPowerOfTwoTest.java new file mode 100644 index 000000000000..34391002941b --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/HigherLowerPowerOfTwoTest.java @@ -0,0 +1,26 @@ +package com.thealgorithms.bitmanipulation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class HigherLowerPowerOfTwoTest { + + @Test + public void testNextHigherPowerOfTwo() { + assertEquals(32, HigherLowerPowerOfTwo.nextHigherPowerOfTwo(19)); // next higher power of two is 32 + assertEquals(1, HigherLowerPowerOfTwo.nextHigherPowerOfTwo(1)); // next higher power of two is 1 + assertEquals(16, HigherLowerPowerOfTwo.nextHigherPowerOfTwo(15)); // next higher power of two is 16 + assertEquals(8, HigherLowerPowerOfTwo.nextHigherPowerOfTwo(8)); // next higher power of two is 8 + assertEquals(16, HigherLowerPowerOfTwo.nextHigherPowerOfTwo(9)); // next higher power of two is 16 + } + + @Test + public void testNextLowerPowerOfTwo() { + assertEquals(16, HigherLowerPowerOfTwo.nextLowerPowerOfTwo(19)); // next lower power of two is 16 + assertEquals(1, HigherLowerPowerOfTwo.nextLowerPowerOfTwo(1)); // next lower power of two is 1 + assertEquals(8, HigherLowerPowerOfTwo.nextLowerPowerOfTwo(9)); // next lower power of two is 8 + assertEquals(8, HigherLowerPowerOfTwo.nextLowerPowerOfTwo(15)); // next lower power of two is 8 + assertEquals(8, HigherLowerPowerOfTwo.nextLowerPowerOfTwo(8)); // next lower power of two is 8 + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/IsPowerTwoTest.java b/src/test/java/com/thealgorithms/bitmanipulation/IsPowerTwoTest.java index 27bc93c31ae4..ffd9e4ef176d 100644 --- a/src/test/java/com/thealgorithms/bitmanipulation/IsPowerTwoTest.java +++ b/src/test/java/com/thealgorithms/bitmanipulation/IsPowerTwoTest.java @@ -3,7 +3,10 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.Test; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; /** * Test case for IsPowerTwo class @@ -11,25 +14,38 @@ */ public class IsPowerTwoTest { - @Test - public void testIsPowerTwo() { - // test some positive powers of 2 - assertTrue(IsPowerTwo.isPowerTwo(1)); - assertTrue(IsPowerTwo.isPowerTwo(2)); - assertTrue(IsPowerTwo.isPowerTwo(4)); - assertTrue(IsPowerTwo.isPowerTwo(16)); - assertTrue(IsPowerTwo.isPowerTwo(1024)); - // test some negative numbers - assertFalse(IsPowerTwo.isPowerTwo(-1)); - assertFalse(IsPowerTwo.isPowerTwo(-2)); - assertFalse(IsPowerTwo.isPowerTwo(-4)); + @ParameterizedTest + @MethodSource("provideNumbersForPowerTwo") + public void testIsPowerTwo(int number, boolean expected) { + if (expected) { + assertTrue(IsPowerTwo.isPowerTwo(number)); + } else { + assertFalse(IsPowerTwo.isPowerTwo(number)); + } + } - // test some numbers that are not powers of 2 - assertFalse(IsPowerTwo.isPowerTwo(0)); - assertFalse(IsPowerTwo.isPowerTwo(3)); - assertFalse(IsPowerTwo.isPowerTwo(5)); - assertFalse(IsPowerTwo.isPowerTwo(15)); - assertFalse(IsPowerTwo.isPowerTwo(1000)); + private static Stream provideNumbersForPowerTwo() { + return Stream.of(Arguments.of(1, Boolean.TRUE), // 2^0 + Arguments.of(2, Boolean.TRUE), // 2^1 + Arguments.of(4, Boolean.TRUE), // 2^2 + Arguments.of(8, Boolean.TRUE), // 2^3 + Arguments.of(16, Boolean.TRUE), // 2^4 + Arguments.of(32, Boolean.TRUE), // 2^5 + Arguments.of(64, Boolean.TRUE), // 2^6 + Arguments.of(128, Boolean.TRUE), // 2^7 + Arguments.of(256, Boolean.TRUE), // 2^8 + Arguments.of(1024, Boolean.TRUE), // 2^10 + Arguments.of(0, Boolean.FALSE), // 0 is not a power of two + Arguments.of(-1, Boolean.FALSE), // Negative number + Arguments.of(-2, Boolean.FALSE), // Negative number + Arguments.of(-4, Boolean.FALSE), // Negative number + Arguments.of(3, Boolean.FALSE), // 3 is not a power of two + Arguments.of(5, Boolean.FALSE), // 5 is not a power of two + Arguments.of(6, Boolean.FALSE), // 6 is not a power of two + Arguments.of(15, Boolean.FALSE), // 15 is not a power of two + Arguments.of(1000, Boolean.FALSE), // 1000 is not a power of two + Arguments.of(1023, Boolean.FALSE) // 1023 is not a power of two + ); } } diff --git a/src/test/java/com/thealgorithms/bitmanipulation/ModuloPowerOfTwoTest.java b/src/test/java/com/thealgorithms/bitmanipulation/ModuloPowerOfTwoTest.java new file mode 100644 index 000000000000..ff7f621a64c0 --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/ModuloPowerOfTwoTest.java @@ -0,0 +1,38 @@ +package com.thealgorithms.bitmanipulation; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class ModuloPowerOfTwoTest { + + @ParameterizedTest + @CsvSource({ + "10, 3, 2", + "15, 2, 3", + "20, 4, 4", + "7, 1, 1", + "5, 1, 1", + "36, 5, 4", + }) + void + testModuloPowerOfTwo(int x, int n, int expected) { + assertEquals(expected, ModuloPowerOfTwo.moduloPowerOfTwo(x, n)); + } + + @ParameterizedTest + @CsvSource({ + "10, 0", + "15, -2", + "20, -4", + "7, -1", + "5, -1", + }) + void + testNegativeExponent(int x, int n) { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> ModuloPowerOfTwo.moduloPowerOfTwo(x, n)); + assertEquals("The exponent must be positive", exception.getMessage()); + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/NextHigherSameBitCountTest.java b/src/test/java/com/thealgorithms/bitmanipulation/NextHigherSameBitCountTest.java new file mode 100644 index 000000000000..c8fb9ef21b60 --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/NextHigherSameBitCountTest.java @@ -0,0 +1,22 @@ +package com.thealgorithms.bitmanipulation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class NextHigherSameBitCountTest { + + @ParameterizedTest + @CsvSource({ + "5, 6", // 101 -> 110 + "7, 11", // 0111 -> 1011 + "3, 5", // 011 -> 101 + "12, 17", // 001100 -> 010001 + "15, 23" // 01111 -> 10111 + }) + void + testNextHigherSameBitCount(int input, int expected) { + assertEquals(expected, NextHigherSameBitCount.nextHigherSameBitCount(input)); + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinderTest.java b/src/test/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinderTest.java index 1addde057181..fe2136fd7f04 100644 --- a/src/test/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinderTest.java +++ b/src/test/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinderTest.java @@ -2,22 +2,35 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; /** * Test case for Non Repeating Number Finder + * This test class validates the functionality of the + * NonRepeatingNumberFinder by checking various scenarios. + * * @author Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi) */ - class NonRepeatingNumberFinderTest { - @Test - void testNonRepeatingNumberFinder() { - int[] arr = {1, 2, 1, 2, 6}; - assertEquals(6, NonRepeatingNumberFinder.findNonRepeatingNumber(arr)); - int[] arr1 = {1, 2, 1, 2}; - assertEquals(0, NonRepeatingNumberFinder.findNonRepeatingNumber(arr1)); - int[] arr2 = {12}; - assertEquals(12, NonRepeatingNumberFinder.findNonRepeatingNumber(arr2)); + @ParameterizedTest + @MethodSource("testCases") + void testNonRepeatingNumberFinder(int[] arr, int expected) { + assertEquals(expected, NonRepeatingNumberFinder.findNonRepeatingNumber(arr)); + } + + private static Arguments[] testCases() { + return new Arguments[] { + Arguments.of(new int[] {1, 2, 1, 2, 6}, 6), Arguments.of(new int[] {1, 2, 1, 2}, 0), // All numbers repeat + Arguments.of(new int[] {12}, 12), // Single non-repeating number + Arguments.of(new int[] {3, 5, 3, 4, 4}, 5), // More complex case + Arguments.of(new int[] {7, 8, 7, 9, 8, 10, 10}, 9), // Non-repeating in the middle + Arguments.of(new int[] {0, -1, 0, -1, 2}, 2), // Testing with negative numbers + Arguments.of(new int[] {Integer.MAX_VALUE, 1, 1}, Integer.MAX_VALUE), // Edge case with max int + Arguments.of(new int[] {2, 2, 3, 3, 4, 5, 4}, 5), // Mixed duplicates + Arguments.of(new int[] {}, 0) // Edge case: empty array (should be handled as per design) + }; } } diff --git a/src/test/java/com/thealgorithms/bitmanipulation/NumberAppearingOddTimesTest.java b/src/test/java/com/thealgorithms/bitmanipulation/NumberAppearingOddTimesTest.java new file mode 100644 index 000000000000..83d458319f3b --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/NumberAppearingOddTimesTest.java @@ -0,0 +1,49 @@ +package com.thealgorithms.bitmanipulation; + +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 NumberAppearingOddTimesTest { + + /** + * Parameterized test for findOddOccurrence method. Tests multiple + * input arrays and their expected results. + */ + @ParameterizedTest + @MethodSource("provideTestCases") + void testFindOddOccurrence(int[] input, int expected) { + assertEquals(expected, NumberAppearingOddTimes.findOddOccurrence(input)); + } + + /** + * Provides test cases for the parameterized test. + * Each test case consists of an input array and the expected result. + */ + private static Stream provideTestCases() { + return Stream.of( + // Single element appearing odd times (basic case) + Arguments.of(new int[] {5, 6, 7, 8, 6, 7, 5}, 8), + + // More complex case with multiple pairs + Arguments.of(new int[] {2, 3, 5, 4, 5, 2, 4, 3, 5, 2, 4, 4, 2}, 5), + + // Case with only one element appearing once + Arguments.of(new int[] {10, 10, 20, 20, 30}, 30), + + // Negative numbers with an odd occurrence + Arguments.of(new int[] {-5, -5, -3, -3, -7, -7, -7}, -7), + + // All elements cancel out to 0 (even occurrences of all elements) + Arguments.of(new int[] {1, 2, 1, 2}, 0), + + // Array with a single element (trivial case) + Arguments.of(new int[] {42}, 42), + + // Large array with repeated patterns + Arguments.of(new int[] {1, 1, 2, 2, 3, 3, 3, 4, 4}, 3)); + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/NumbersDifferentSignsTest.java b/src/test/java/com/thealgorithms/bitmanipulation/NumbersDifferentSignsTest.java index f54070d39cdd..33e5ae048814 100644 --- a/src/test/java/com/thealgorithms/bitmanipulation/NumbersDifferentSignsTest.java +++ b/src/test/java/com/thealgorithms/bitmanipulation/NumbersDifferentSignsTest.java @@ -3,30 +3,44 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.Test; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + /** - * test Cases of Numbers Different Signs + * Parameterized tests for NumbersDifferentSigns class, which checks + * if two integers have different signs using bitwise XOR. + * * @author Bama Charan Chhandogi */ class NumbersDifferentSignsTest { - @Test - void testDifferentSignsPositiveNegative() { - assertTrue(NumbersDifferentSigns.differentSigns(2, -1)); + @ParameterizedTest + @MethodSource("provideTestCases") + void testDifferentSigns(int num1, int num2, boolean expected) { + if (expected) { + assertTrue(NumbersDifferentSigns.differentSigns(num1, num2)); + } else { + assertFalse(NumbersDifferentSigns.differentSigns(num1, num2)); + } } - @Test - void testDifferentSignsNegativePositive() { - assertTrue(NumbersDifferentSigns.differentSigns(-3, 7)); - } + private static Stream provideTestCases() { + return Stream.of( + // Different signs (positive and negative) + Arguments.of(2, -1, Boolean.TRUE), Arguments.of(-3, 7, Boolean.TRUE), - @Test - void testSameSignsPositive() { - assertFalse(NumbersDifferentSigns.differentSigns(10, 20)); - } + // Same signs (both positive) + Arguments.of(10, 20, Boolean.FALSE), Arguments.of(0, 5, Boolean.FALSE), // 0 is considered non-negative + + // Same signs (both negative) + Arguments.of(-5, -8, Boolean.FALSE), + + // Edge case: Large positive and negative values + Arguments.of(Integer.MAX_VALUE, Integer.MIN_VALUE, Boolean.TRUE), - @Test - void testSameSignsNegative() { - assertFalse(NumbersDifferentSigns.differentSigns(-5, -8)); + // Edge case: Same number (positive and negative) + Arguments.of(-42, -42, Boolean.FALSE), Arguments.of(42, 42, Boolean.FALSE)); } } diff --git a/src/test/java/com/thealgorithms/bitmanipulation/OneBitDifferenceTest.java b/src/test/java/com/thealgorithms/bitmanipulation/OneBitDifferenceTest.java new file mode 100644 index 000000000000..06b8d32d3406 --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/OneBitDifferenceTest.java @@ -0,0 +1,15 @@ +package com.thealgorithms.bitmanipulation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +public class OneBitDifferenceTest { + + @ParameterizedTest + @CsvSource({"7, 5, true", "3, 2, true", "10, 8, true", "15, 15, false", "4, 1, false"}) + void testDifferByOneBit(int x, int y, boolean expected) { + assertEquals(expected, OneBitDifference.differByOneBit(x, y)); + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/OnesComplementTest.java b/src/test/java/com/thealgorithms/bitmanipulation/OnesComplementTest.java new file mode 100644 index 000000000000..6be4eb595f79 --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/OnesComplementTest.java @@ -0,0 +1,47 @@ +package com.thealgorithms.bitmanipulation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Test case for Highest Set Bit + * @author Abhinay Verma(https://github.com/Monk-AbhinayVerma) + */ +public class OnesComplementTest { + + @Test + public void testOnesComplementAllZeroes() { + + // Test cases with all-zero binary strings + assertEquals("1111", OnesComplement.onesComplement("0000")); + assertEquals("111", OnesComplement.onesComplement("000")); + assertEquals("11", OnesComplement.onesComplement("00")); + assertEquals("1", OnesComplement.onesComplement("0")); + } + + @Test + public void testOnesComplementAllOnes() { + // Test cases with all-one binary strings + assertEquals("0000", OnesComplement.onesComplement("1111")); + assertEquals("000", OnesComplement.onesComplement("111")); + assertEquals("00", OnesComplement.onesComplement("11")); + assertEquals("0", OnesComplement.onesComplement("1")); + } + + @Test + public void testOnesComplementMixedBits() { + // Test more mixed binary patterns + assertEquals("1010", OnesComplement.onesComplement("0101")); + assertEquals("0101", OnesComplement.onesComplement("1010")); + assertEquals("1100", OnesComplement.onesComplement("0011")); + assertEquals("0011", OnesComplement.onesComplement("1100")); + assertEquals("1001", OnesComplement.onesComplement("0110")); + } + + @Test + public void testOnesComplementEmptyString() { + // Test empty string scenario + assertEquals("", OnesComplement.onesComplement("")); + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/ParityCheckTest.java b/src/test/java/com/thealgorithms/bitmanipulation/ParityCheckTest.java new file mode 100644 index 000000000000..90147a61207b --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/ParityCheckTest.java @@ -0,0 +1,15 @@ +package com.thealgorithms.bitmanipulation; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class ParityCheckTest { + @Test + public void testIsOddParity() { + assertTrue(ParityCheck.checkParity(5)); // 101 has 2 ones (even parity) + assertFalse(ParityCheck.checkParity(7)); // 111 has 3 ones (odd parity) + assertFalse(ParityCheck.checkParity(8)); // 1000 has 1 one (odd parity) + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/ReverseBitsTest.java b/src/test/java/com/thealgorithms/bitmanipulation/ReverseBitsTest.java index 967a89a1ee97..5cfa82355ce7 100644 --- a/src/test/java/com/thealgorithms/bitmanipulation/ReverseBitsTest.java +++ b/src/test/java/com/thealgorithms/bitmanipulation/ReverseBitsTest.java @@ -2,14 +2,40 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.Test; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; class ReverseBitsTest { - @Test - void testReverseBits() { - assertEquals(0, ReverseBits.reverseBits(0)); - assertEquals(-1, ReverseBits.reverseBits(-1)); - assertEquals(964176192, ReverseBits.reverseBits(43261596)); + @ParameterizedTest + @MethodSource("provideTestCases") + void testReverseBits(int input, int expected) { + assertEquals(expected, ReverseBits.reverseBits(input)); + } + + private static Stream provideTestCases() { + return Stream.of( + // Edge case: All bits are 0 + Arguments.of(0, 0), + + // Edge case: All bits are 1 (Two’s complement representation of -1) + Arguments.of(-1, -1), + + // Case with random number 43261596 + Arguments.of(43261596, 964176192), + + // Case with maximum positive value for 32-bit integer + Arguments.of(Integer.MAX_VALUE, -2), + + // Case with minimum value (all bits 1 except the sign bit) + Arguments.of(Integer.MIN_VALUE, 1), + + // Case with a single bit set (2^0 = 1) + Arguments.of(1, Integer.MIN_VALUE), + + // Case with alternating bits: 0b101010...10 (in binary) + Arguments.of(0xAAAAAAAA, 0x55555555)); } } diff --git a/src/test/java/com/thealgorithms/bitmanipulation/SingleBitOperationsTest.java b/src/test/java/com/thealgorithms/bitmanipulation/SingleBitOperationsTest.java index 9cac8d670a4a..6c447ec9805e 100644 --- a/src/test/java/com/thealgorithms/bitmanipulation/SingleBitOperationsTest.java +++ b/src/test/java/com/thealgorithms/bitmanipulation/SingleBitOperationsTest.java @@ -2,35 +2,60 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.Test; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; -public class SingleBitOperationsTest { +class SingleBitOperationsTest { - @Test - public void flipBitTest() { - assertEquals(1, SingleBitOperations.flipBit(3, 1)); - assertEquals(11, SingleBitOperations.flipBit(3, 3)); + @ParameterizedTest + @MethodSource("provideFlipBitTestCases") + void testFlipBit(int input, int bit, int expected) { + assertEquals(expected, SingleBitOperations.flipBit(input, bit)); } - @Test - public void setBitTest() { - assertEquals(5, SingleBitOperations.setBit(4, 0)); - assertEquals(4, SingleBitOperations.setBit(4, 2)); - assertEquals(5, SingleBitOperations.setBit(5, 0)); - assertEquals(14, SingleBitOperations.setBit(10, 2)); - assertEquals(15, SingleBitOperations.setBit(15, 3)); - assertEquals(2, SingleBitOperations.setBit(0, 1)); + private static Stream provideFlipBitTestCases() { + return Stream.of(Arguments.of(3, 1, 1), // Binary: 11 -> 01 + Arguments.of(3, 3, 11) // Binary: 11 -> 1011 + ); } - @Test - public void clearBitTest() { - assertEquals(5, SingleBitOperations.clearBit(7, 1)); - assertEquals(5, SingleBitOperations.clearBit(5, 1)); + @ParameterizedTest + @MethodSource("provideSetBitTestCases") + void testSetBit(int input, int bit, int expected) { + assertEquals(expected, SingleBitOperations.setBit(input, bit)); } - @Test - public void getBitTest() { - assertEquals(0, SingleBitOperations.getBit(6, 0)); - assertEquals(1, SingleBitOperations.getBit(7, 1)); + private static Stream provideSetBitTestCases() { + return Stream.of(Arguments.of(4, 0, 5), // 100 -> 101 + Arguments.of(4, 2, 4), // 100 -> 100 (bit already set) + Arguments.of(0, 1, 2), // 00 -> 10 + Arguments.of(10, 2, 14) // 1010 -> 1110 + ); + } + + @ParameterizedTest + @MethodSource("provideClearBitTestCases") + void testClearBit(int input, int bit, int expected) { + assertEquals(expected, SingleBitOperations.clearBit(input, bit)); + } + + private static Stream provideClearBitTestCases() { + return Stream.of(Arguments.of(7, 1, 5), // 111 -> 101 + Arguments.of(5, 1, 5) // 101 -> 101 (bit already cleared) + ); + } + + @ParameterizedTest + @MethodSource("provideGetBitTestCases") + void testGetBit(int input, int bit, int expected) { + assertEquals(expected, SingleBitOperations.getBit(input, bit)); + } + + private static Stream provideGetBitTestCases() { + return Stream.of(Arguments.of(6, 0, 0), // 110 -> Bit 0: 0 + Arguments.of(7, 1, 1) // 111 -> Bit 1: 1 + ); } } diff --git a/src/test/java/com/thealgorithms/bitmanipulation/SingleElementTest.java b/src/test/java/com/thealgorithms/bitmanipulation/SingleElementTest.java new file mode 100644 index 000000000000..2e2afe99b0da --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/SingleElementTest.java @@ -0,0 +1,33 @@ +package com.thealgorithms.bitmanipulation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public final class SingleElementTest { + + /** + * Parameterized test to find the single non-duplicate element + * in the given arrays. + * + * @param arr the input array where every element appears twice except one + * @param expected the expected single element result + */ + @ParameterizedTest + @MethodSource("provideTestCases") + void testFindSingleElement(int[] arr, int expected) { + assertEquals(expected, SingleElement.findSingleElement(arr)); + } + + /** + * Provides test cases for the parameterized test. + * + * @return Stream of arguments consisting of arrays and expected results + */ + private static Stream provideTestCases() { + return Stream.of(Arguments.of(new int[] {1, 1, 2, 2, 4, 4, 3}, 3), Arguments.of(new int[] {1, 2, 2, 3, 3}, 1), Arguments.of(new int[] {10}, 10)); + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/SwapAdjacentBitsTest.java b/src/test/java/com/thealgorithms/bitmanipulation/SwapAdjacentBitsTest.java new file mode 100644 index 000000000000..12f0542b92f6 --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/SwapAdjacentBitsTest.java @@ -0,0 +1,29 @@ +package com.thealgorithms.bitmanipulation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class SwapAdjacentBitsTest { + + @ParameterizedTest + @CsvSource({"2, 1", // 2 (binary: 10) -> 1 (binary: 01) + "43, 23", // 43 (binary: 101011) -> 23 (binary: 010111) + "153, 102", // 153 (binary: 10011001) -> 102 (binary: 01100110) + "15, 15", // 15 (binary: 1111) -> 15 (binary: 1111) (no change) + "0, 0", // 0 (binary: 0000) -> 0 (binary: 0000) (no change) + "1, 2", // 1 (binary: 01) -> 2 (binary: 10) + "170, 85", // 170 (binary: 10101010) -> 85 (binary: 01010101) + "85, 170", // 85 (binary: 01010101) -> 170 (binary: 10101010) + "255, 255", // 255 (binary: 11111111) -> 255 (binary: 11111111) (no change) + "128, 64", // 128 (binary: 10000000) -> 64 (binary: 01000000) + "1024, 2048", + "-1, -1", // -1 (all bits 1) remains -1 (no change due to two's complement) + "-2, -3", // -2 (binary: ...1110) -> -3 (binary: ...1101) + "2147483647, -1073741825", "-2147483648, -1073741824"}) + void + testSwapAdjacentBits(int input, int expected) { + assertEquals(expected, SwapAdjacentBits.swapAdjacentBits(input)); + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/TwosComplementTest.java b/src/test/java/com/thealgorithms/bitmanipulation/TwosComplementTest.java new file mode 100644 index 000000000000..578acc7af18c --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/TwosComplementTest.java @@ -0,0 +1,64 @@ +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; + +/** + * Test case for Highest Set Bit + * @author Abhinay Verma(https://github.com/Monk-AbhinayVerma) + */ +public class TwosComplementTest { + + @Test + public void testTwosComplementAllZeroes() { + assertEquals("10000", TwosComplement.twosComplement("0000")); + assertEquals("1000", TwosComplement.twosComplement("000")); + assertEquals("100", TwosComplement.twosComplement("00")); + assertEquals("10", TwosComplement.twosComplement("0")); + } + + @Test + public void testTwosComplementAllOnes() { + assertEquals("00001", TwosComplement.twosComplement("11111")); + assertEquals("0001", TwosComplement.twosComplement("1111")); + assertEquals("001", TwosComplement.twosComplement("111")); + assertEquals("01", TwosComplement.twosComplement("11")); + } + + @Test + public void testTwosComplementMixedBits() { + assertEquals("1111", TwosComplement.twosComplement("0001")); // 1 -> 1111 + assertEquals("1001", TwosComplement.twosComplement("0111")); // 0111 -> 1001 + assertEquals("11001", TwosComplement.twosComplement("00111")); // 00111 -> 11001 + assertEquals("011", TwosComplement.twosComplement("101")); // 101 -> 011 + } + + @Test + public void testTwosComplementSingleBit() { + assertEquals("10", TwosComplement.twosComplement("0")); // 0 -> 10 + assertEquals("1", TwosComplement.twosComplement("1")); // 1 -> 1 + } + + @Test + public void testTwosComplementWithLeadingZeroes() { + assertEquals("1111", TwosComplement.twosComplement("0001")); // 0001 -> 1111 + assertEquals("101", TwosComplement.twosComplement("011")); // 011 -> 101 + assertEquals("110", TwosComplement.twosComplement("010")); // 010 -> 110 + } + + @Test + public void testInvalidBinaryInput() { + // Test for invalid input that contains non-binary characters. + assertThrows(IllegalArgumentException.class, () -> TwosComplement.twosComplement("102")); + assertThrows(IllegalArgumentException.class, () -> TwosComplement.twosComplement("abc")); + assertThrows(IllegalArgumentException.class, () -> TwosComplement.twosComplement("10a01")); + } + + @Test + public void testEmptyInput() { + // Edge case: Empty input should result in an IllegalArgumentException. + assertThrows(IllegalArgumentException.class, () -> TwosComplement.twosComplement("")); + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/Xs3ConversionTest.java b/src/test/java/com/thealgorithms/bitmanipulation/Xs3ConversionTest.java new file mode 100644 index 000000000000..2e3242decba0 --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/Xs3ConversionTest.java @@ -0,0 +1,65 @@ +package com.thealgorithms.bitmanipulation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the Xs3Conversion class. + */ +public class Xs3ConversionTest { + + /** + * Test the xs3ToBinary method with an XS-3 number. + */ + @Test + public void testXs3ToBinary() { + int binary = Xs3Conversion.xs3ToBinary(0x4567); + assertEquals(1234, binary); // XS-3 0x4567 should convert to binary 1234 + } + + /** + * Test the binaryToXs3 method with a binary number. + */ + @Test + public void testBinaryToXs3() { + int xs3 = Xs3Conversion.binaryToXs3(1234); + assertEquals(0x4567, xs3); // Binary 1234 should convert to XS-3 0x4567 + } + + /** + * Test the xs3ToBinary method with zero. + */ + @Test + public void testXs3ToBinaryZero() { + int binary = Xs3Conversion.xs3ToBinary(0x0); + assertEquals(0, binary); // XS-3 0x0 should convert to binary 0 + } + + /** + * Test the binaryToXs3 method with zero. + */ + @Test + public void testBinaryToXs3Zero() { + int xs3 = Xs3Conversion.binaryToXs3(0); + assertEquals(0x0, xs3); // Binary 0 should convert to XS-3 0x0 + } + + /** + * Test the xs3ToBinary method with a single digit XS-3 number. + */ + @Test + public void testXs3ToBinarySingleDigit() { + int binary = Xs3Conversion.xs3ToBinary(0x5); + assertEquals(2, binary); // XS-3 0x5 should convert to binary 2 + } + + /** + * Test the binaryToXs3 method with a single digit binary number. + */ + @Test + public void testBinaryToXs3SingleDigit() { + int xs3 = Xs3Conversion.binaryToXs3(2); + assertEquals(0x5, xs3); // Binary 2 should convert to XS-3 0x5 + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/ADFGVXCipherTest.java b/src/test/java/com/thealgorithms/ciphers/ADFGVXCipherTest.java new file mode 100644 index 000000000000..4db856e40b84 --- /dev/null +++ b/src/test/java/com/thealgorithms/ciphers/ADFGVXCipherTest.java @@ -0,0 +1,46 @@ +package com.thealgorithms.ciphers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class ADFGVXCipherTest { + + private final ADFGVXCipher adfgvxCipher = new ADFGVXCipher(); + + @Test + void testEncrypt() { + String message = "attack at 1200am"; + String key = "PRIVACY"; + + String encrypted = adfgvxCipher.encrypt(message, key); + assertEquals("DGDDDAGDDGAFADDFDADVDVFAADVX", encrypted); + } + + @Test + void testDecrypt() { + String encrypted = "DGDDDAGDDGAFADDFDADVDVFAADVX"; + String key = "PRIVACY"; + + String decrypted = adfgvxCipher.decrypt(encrypted, key); + assertEquals("ATTACKAT1200AM", decrypted); + } + + @Test + void testEmptyInput() { + String encrypted = adfgvxCipher.encrypt("", "PRIVACY"); + String decrypted = adfgvxCipher.decrypt("", "PRIVACY"); + assertEquals("", encrypted); + assertEquals("", decrypted); + } + + @Test + void testShortKey() { + String message = "TESTING"; + String key = "A"; + + String encrypted = adfgvxCipher.encrypt(message, key); + String decrypted = adfgvxCipher.decrypt(encrypted, key); + assertEquals("TESTING", decrypted); + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/AtbashTest.java b/src/test/java/com/thealgorithms/ciphers/AtbashTest.java new file mode 100644 index 000000000000..ec34bc26ad72 --- /dev/null +++ b/src/test/java/com/thealgorithms/ciphers/AtbashTest.java @@ -0,0 +1,43 @@ +package com.thealgorithms.ciphers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class AtbashTest { + + @ParameterizedTest + @MethodSource("cipherTestProvider") + public void testAtbashCipher(String input, String expected) { + AtbashCipher cipher = new AtbashCipher(input); + assertEquals(expected, cipher.convert()); + } + + private static Stream cipherTestProvider() { + return Stream.of( + // Basic tests with lowercase and uppercase + Arguments.of("Hello", "Svool"), Arguments.of("WORLD", "DLIOW"), + + // Mixed case with spaces and punctuation + Arguments.of("Hello World!", "Svool Dliow!"), Arguments.of("123 ABC xyz", "123 ZYX cba"), + + // Palindromes and mixed cases + Arguments.of("madam", "nzwzn"), Arguments.of("Palindrome", "Kzormwilnv"), + + // Non-alphabetic characters should remain unchanged + Arguments.of("@cipher 123!", "@xrksvi 123!"), Arguments.of("no-change", "ml-xszmtv"), + + // Empty string and single characters + Arguments.of("", ""), Arguments.of("A", "Z"), Arguments.of("z", "a"), + + // Numbers and symbols + Arguments.of("!@#123", "!@#123"), + + // Full sentence with uppercase, lowercase, symbols, and numbers + Arguments.of("Hello World! 123, @cipher abcDEF ZYX 987 madam zzZ Palindrome!", "Svool Dliow! 123, @xrksvi zyxWVU ABC 987 nzwzn aaA Kzormwilnv!"), + Arguments.of("Svool Dliow! 123, @xrksvi zyxWVU ABC 987 nzwzn aaA Kzormwilnv!", "Hello World! 123, @cipher abcDEF ZYX 987 madam zzZ Palindrome!")); + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/BaconianCipherTest.java b/src/test/java/com/thealgorithms/ciphers/BaconianCipherTest.java new file mode 100644 index 000000000000..bb1ae5a7e4c2 --- /dev/null +++ b/src/test/java/com/thealgorithms/ciphers/BaconianCipherTest.java @@ -0,0 +1,34 @@ +package com.thealgorithms.ciphers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class BaconianCipherTest { + + BaconianCipher baconianCipher = new BaconianCipher(); + + @Test + void baconianCipherEncryptTest() { + // given + String plaintext = "MEET AT DAWN"; + + // when + String cipherText = baconianCipher.encrypt(plaintext); + + // then + assertEquals("ABBAAAABAAAABAABAABBAAAAABAABBAAABBAAAAABABBAABBAB", cipherText); + } + + @Test + void baconianCipherDecryptTest() { + // given + String ciphertext = "ABBAAAABAAAABAABAABBAAAAABAABBAAABBAAAAABABBAABBAB"; + + // when + String plainText = baconianCipher.decrypt(ciphertext); + + // then + assertEquals("MEETATDAWN", plainText); + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/ColumnarTranspositionCipherTest.java b/src/test/java/com/thealgorithms/ciphers/ColumnarTranspositionCipherTest.java index 0d306a6623db..8deae45099d6 100644 --- a/src/test/java/com/thealgorithms/ciphers/ColumnarTranspositionCipherTest.java +++ b/src/test/java/com/thealgorithms/ciphers/ColumnarTranspositionCipherTest.java @@ -20,7 +20,7 @@ public void setUp() { @Test public void testEncryption() { - String encryptedText = ColumnarTranspositionCipher.encrpyter(plaintext, keyword); + String encryptedText = ColumnarTranspositionCipher.encrypt(plaintext, keyword); assertNotNull(encryptedText, "The encrypted text should not be null."); assertFalse(encryptedText.isEmpty(), "The encrypted text should not be empty."); // Check if the encrypted text is different from the plaintext @@ -29,19 +29,19 @@ public void testEncryption() { @Test public void testDecryption() { - String encryptedText = ColumnarTranspositionCipher.encrpyter(plaintext, keyword); - String decryptedText = ColumnarTranspositionCipher.decrypter(); + String encryptedText = ColumnarTranspositionCipher.encrypt(plaintext, keyword); + String decryptedText = ColumnarTranspositionCipher.decrypt(); assertEquals(plaintext.replaceAll(" ", ""), decryptedText.replaceAll(" ", ""), "The decrypted text should match the original plaintext, ignoring spaces."); - assertEquals(encryptedText, ColumnarTranspositionCipher.encrpyter(plaintext, keyword), "The encrypted text should be the same when encrypted again."); + assertEquals(encryptedText, ColumnarTranspositionCipher.encrypt(plaintext, keyword), "The encrypted text should be the same when encrypted again."); } @Test public void testLongPlainText() { String longText = "This is a significantly longer piece of text to test the encryption and decryption capabilities of the Columnar Transposition Cipher. It should handle long strings gracefully."; - String encryptedText = ColumnarTranspositionCipher.encrpyter(longText, keyword); - String decryptedText = ColumnarTranspositionCipher.decrypter(); + String encryptedText = ColumnarTranspositionCipher.encrypt(longText, keyword); + String decryptedText = ColumnarTranspositionCipher.decrypt(); assertEquals(longText.replaceAll(" ", ""), decryptedText.replaceAll(" ", ""), "The decrypted text should match the original long plaintext, ignoring spaces."); - assertEquals(encryptedText, ColumnarTranspositionCipher.encrpyter(longText, keyword), "The encrypted text should be the same when encrypted again."); + assertEquals(encryptedText, ColumnarTranspositionCipher.encrypt(longText, keyword), "The encrypted text should be the same when encrypted again."); } } diff --git a/src/test/java/com/thealgorithms/ciphers/DiffieHellmanTest.java b/src/test/java/com/thealgorithms/ciphers/DiffieHellmanTest.java new file mode 100644 index 000000000000..6255ad22ab56 --- /dev/null +++ b/src/test/java/com/thealgorithms/ciphers/DiffieHellmanTest.java @@ -0,0 +1,38 @@ +package com.thealgorithms.ciphers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.math.BigInteger; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class DiffieHellmanTest { + + // Test for public value calculation using instance methods + @ParameterizedTest + @MethodSource("provideTestData") + public void testCalculatePublicValue(BigInteger base, BigInteger secret, BigInteger prime, BigInteger publicExpected, BigInteger sharedExpected) { + DiffieHellman dh = new DiffieHellman(base, secret, prime); // Create an instance of DiffieHellman + assertEquals(publicExpected, dh.calculatePublicValue()); // Call instance method + } + + // Test for shared secret calculation using instance methods + @ParameterizedTest + @MethodSource("provideTestData") + public void testCalculateSharedSecret(BigInteger base, BigInteger secret, BigInteger prime, BigInteger publicExpected, BigInteger sharedExpected) { + DiffieHellman dh = new DiffieHellman(base, secret, prime); // Create an instance of DiffieHellman + assertEquals(sharedExpected, dh.calculateSharedSecret(publicExpected)); // Call instance method + } + + // Provide test data for both public key and shared secret calculation + private static Stream provideTestData() { + return Stream.of(createTestArgs(5, 6, 23, 8, 13), createTestArgs(2, 5, 13, 6, 2)); + } + + // Helper method for arguments + private static Arguments createTestArgs(long base, long secret, long prime, long publicExpected, long sharedExpected) { + return Arguments.of(BigInteger.valueOf(base), BigInteger.valueOf(secret), BigInteger.valueOf(prime), BigInteger.valueOf(publicExpected), BigInteger.valueOf(sharedExpected)); + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/ECCTest.java b/src/test/java/com/thealgorithms/ciphers/ECCTest.java new file mode 100644 index 000000000000..701f801af1c8 --- /dev/null +++ b/src/test/java/com/thealgorithms/ciphers/ECCTest.java @@ -0,0 +1,106 @@ +package com.thealgorithms.ciphers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.math.BigInteger; +import org.junit.jupiter.api.Test; + +/** + * ECCTest - Unit tests for the ECC (Elliptic Curve Cryptography) implementation. + * This class contains various test cases to validate the encryption and decryption functionalities. + * It ensures the correctness and randomness of ECC operations. + * + * @author xuyang + */ +public class ECCTest { + ECC ecc = new ECC(256); // Generate a 256-bit ECC key pair. Calls generateKeys(bits) to create keys including privateKey and publicKey. + + /** + * Test the encryption functionality: convert plaintext to ciphertext and output relevant encryption data. + */ + @Test + void testEncrypt() { + String textToEncrypt = "Elliptic Curve Cryptography"; + + ECC.ECPoint[] cipherText = ecc.encrypt(textToEncrypt); // Perform encryption + + // Output private key information + System.out.println("Private Key: " + ecc.getPrivateKey()); + + // Output elliptic curve parameters + ECC.EllipticCurve curve = ecc.getCurve(); + System.out.println("Elliptic Curve Parameters:"); + System.out.println("a: " + curve.getA()); + System.out.println("b: " + curve.getB()); + System.out.println("p: " + curve.getP()); + 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) + + // Output the encrypted coordinate points + System.out.println("Encrypted Points:"); + for (ECC.ECPoint point : cipherText) { + System.out.println(point); // Calls ECPoint's toString() method + } + } + + /** + * Test the decryption functionality: convert ciphertext back to plaintext using known private key and elliptic curve parameters. + */ + @Test + void testDecryptWithKnownValues() { + // 1. Define the known private key + BigInteger knownPrivateKey = new BigInteger("28635978664199231399690075483195602260051035216440375973817268759912070302603"); + + // 2. Define the known elliptic curve parameters + BigInteger a = new BigInteger("64505295837372135469230827475895976532873592609649950000895066186842236488761"); // Replace with known a value + BigInteger b = new BigInteger("89111668838830965251111555638616364203833415376750835901427122343021749874324"); // Replace with known b value + BigInteger p = new BigInteger("107276428198310591598877737561885175918069075479103276920057092968372930219921"); // Replace with known p value + ECC.ECPoint basePoint = new ECC.ECPoint(new BigInteger("4"), new BigInteger("8")); // Replace with known base point coordinates + + // 3. Create the elliptic curve object + ECC.EllipticCurve curve = new ECC.EllipticCurve(a, b, p, basePoint); + + // 4. Define the known ciphertext containing two ECPoints (R, S) + ECC.ECPoint rPoint = new ECC.ECPoint(new BigInteger("103077584019003058745849614420912636617007257617156724481937620119667345237687"), new BigInteger("68193862907937248121971710522760893811582068323088661566426323952783362061817")); + ECC.ECPoint sPoint = new ECC.ECPoint(new BigInteger("31932232426664380635434632300383525435115368414929679432313910646436992147798"), new BigInteger("77299754382292904069123203569944908076819220797512755280123348910207308129766")); + ECC.ECPoint[] cipherText = new ECC.ECPoint[] {rPoint, sPoint}; + + // 5. Create an ECC instance and set the private key and curve parameters + ecc.setPrivateKey(knownPrivateKey); // Use setter method to set the private key + ecc.setCurve(curve); // Use setter method to set the elliptic curve + + // 6. Decrypt the known ciphertext + String decryptedMessage = ecc.decrypt(cipherText); + + // 7. Compare the decrypted plaintext with the expected value + String expectedMessage = "Elliptic Curve Cryptography"; // Expected plaintext + assertEquals(expectedMessage, decryptedMessage); + } + + /** + * Test that encrypting the same plaintext with ECC produces different ciphertexts. + */ + @Test + void testCipherTextRandomness() { + String message = "Elliptic Curve Cryptography"; + + ECC.ECPoint[] cipherText1 = ecc.encrypt(message); + ECC.ECPoint[] cipherText2 = ecc.encrypt(message); + + assertNotEquals(cipherText1, cipherText2); // Ensure that the two ciphertexts are different + } + + /** + * Test the entire ECC encryption and decryption process. + */ + @Test + void testECCEncryptionAndDecryption() { + String textToEncrypt = "Elliptic Curve Cryptography"; + ECC.ECPoint[] cipherText = ecc.encrypt(textToEncrypt); + String decryptedText = ecc.decrypt(cipherText); + assertEquals(textToEncrypt, decryptedText); // Verify that the decrypted text matches the original text + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/MonoAlphabeticTest.java b/src/test/java/com/thealgorithms/ciphers/MonoAlphabeticTest.java new file mode 100644 index 000000000000..b1a8c78c952e --- /dev/null +++ b/src/test/java/com/thealgorithms/ciphers/MonoAlphabeticTest.java @@ -0,0 +1,29 @@ +package com.thealgorithms.ciphers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class MonoAlphabeticTest { + + // Test for both encryption and decryption with different keys + @ParameterizedTest + @MethodSource("provideTestData") + public void testEncryptDecrypt(String plainText, String key, String encryptedText) { + // Test encryption + String actualEncrypted = MonoAlphabetic.encrypt(plainText, key); + assertEquals(encryptedText, actualEncrypted, "Encryption failed for input: " + plainText + " with key: " + key); + + // Test decryption + String actualDecrypted = MonoAlphabetic.decrypt(encryptedText, key); + assertEquals(plainText, actualDecrypted, "Decryption failed for input: " + encryptedText + " with key: " + key); + } + + // Provide test data for both encryption and decryption + private static Stream provideTestData() { + return Stream.of(Arguments.of("HELLO", "MNBVCXZLKJHGFDSAPOIUYTREWQ", "LCGGS"), Arguments.of("JAVA", "MNBVCXZLKJHGFDSAPOIUYTREWQ", "JMTM"), Arguments.of("HELLO", "QWERTYUIOPLKJHGFDSAZXCVBNM", "ITKKG"), Arguments.of("JAVA", "QWERTYUIOPLKJHGFDSAZXCVBNM", "PQCQ")); + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/RSATest.java b/src/test/java/com/thealgorithms/ciphers/RSATest.java index c82f68d11f4c..577f56426be8 100644 --- a/src/test/java/com/thealgorithms/ciphers/RSATest.java +++ b/src/test/java/com/thealgorithms/ciphers/RSATest.java @@ -1,23 +1,64 @@ package com.thealgorithms.ciphers; 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; class RSATest { - RSA rsa = new RSA(1024); + private final RSA rsa = new RSA(1024); @Test - void testRSA() { - // given - String textToEncrypt = "Such secure"; + void testEncryptDecryptString() { + String originalMessage = "Such secure"; + String encryptedMessage = rsa.encrypt(originalMessage); + String decryptedMessage = rsa.decrypt(encryptedMessage); + assertEquals(originalMessage, decryptedMessage); + } + + @Test + void testEncryptDecryptBigInteger() { + BigInteger originalMessage = new BigInteger("12345678901234567890"); + BigInteger encryptedMessage = rsa.encrypt(originalMessage); + BigInteger decryptedMessage = rsa.decrypt(encryptedMessage); + assertEquals(originalMessage, decryptedMessage); + } - // when - String cipherText = rsa.encrypt(textToEncrypt); - String decryptedText = rsa.decrypt(cipherText); + @Test + void testEmptyMessage() { + String originalMessage = ""; + assertThrows(IllegalArgumentException.class, () -> rsa.encrypt(originalMessage)); + assertThrows(IllegalArgumentException.class, () -> rsa.decrypt(originalMessage)); + } + + @Test + void testDifferentKeySizes() { + // Testing with 512-bit RSA keys + RSA smallRSA = new RSA(512); + String originalMessage = "Test with smaller key"; - // then - assertEquals("Such secure", decryptedText); + String encryptedMessage = smallRSA.encrypt(originalMessage); + String decryptedMessage = smallRSA.decrypt(encryptedMessage); + + assertEquals(originalMessage, decryptedMessage); + + // Testing with 2048-bit RSA keys + RSA largeRSA = new RSA(2048); + String largeOriginalMessage = "Test with larger key"; + + String largeEncryptedMessage = largeRSA.encrypt(largeOriginalMessage); + String largeDecryptedMessage = largeRSA.decrypt(largeEncryptedMessage); + + assertEquals(largeOriginalMessage, largeDecryptedMessage); + } + + @Test + void testSpecialCharacters() { + String originalMessage = "Hello, RSA! @2024#"; + String encryptedMessage = rsa.encrypt(originalMessage); + String decryptedMessage = rsa.decrypt(encryptedMessage); + assertEquals(originalMessage, decryptedMessage); } } diff --git a/src/test/java/com/thealgorithms/ciphers/RailFenceTest.java b/src/test/java/com/thealgorithms/ciphers/RailFenceTest.java new file mode 100644 index 000000000000..2bfa704e3d0b --- /dev/null +++ b/src/test/java/com/thealgorithms/ciphers/RailFenceTest.java @@ -0,0 +1,62 @@ +package com.thealgorithms.ciphers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class RailFenceTest { + + @Test + void testEncryption() { + RailFenceCipher cipher = new RailFenceCipher(); + + String input = "We are discovered! Flee at once"; + int rails = 3; + String encrypted = cipher.encrypt(input, rails); + assertEquals("Wrivdlaneaedsoee!Fe toc cr e e", encrypted); + + String singleChar = "A"; + int singleRail = 2; + String encryptedSingleChar = cipher.encrypt(singleChar, singleRail); + assertEquals("A", encryptedSingleChar); + + String shortString = "Hello"; + int moreRails = 10; + String encryptedShortString = cipher.encrypt(shortString, moreRails); + assertEquals("Hello", encryptedShortString); + + String inputSingleRail = "Single line"; + int singleRailOnly = 1; + String encryptedSingleRail = cipher.encrypt(inputSingleRail, singleRailOnly); + assertEquals("Single line", encryptedSingleRail); + } + + @Test + void testDecryption() { + RailFenceCipher cipher = new RailFenceCipher(); + + // Scenario 1: Basic decryption with multiple rails + String encryptedInput = "Wrivdlaneaedsoee!Fe toc cr e e"; + int rails = 3; + String decrypted = cipher.decrypt(encryptedInput, rails); + assertEquals("We are discovered! Flee at once", decrypted); + + // Scenario 2: Single character string decryption + String encryptedSingleChar = "A"; + int singleRail = 2; // More than 1 rail + String decryptedSingleChar = cipher.decrypt(encryptedSingleChar, singleRail); + assertEquals("A", decryptedSingleChar); + + // Scenario 3: String length less than the number of rails + String encryptedShortString = "Hello"; + int moreRails = 10; // More rails than characters + String decryptedShortString = cipher.decrypt(encryptedShortString, moreRails); + assertEquals("Hello", decryptedShortString); + + // Scenario 4: Single rail decryption (output should be the same as input) + String encryptedSingleRail = "Single line"; + int singleRailOnly = 1; + String decryptedSingleRail = cipher.decrypt(encryptedSingleRail, singleRailOnly); + assertEquals("Single line", decryptedSingleRail); + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/VigenereTest.java b/src/test/java/com/thealgorithms/ciphers/VigenereTest.java index c5935de95dfa..7f94e5731989 100644 --- a/src/test/java/com/thealgorithms/ciphers/VigenereTest.java +++ b/src/test/java/com/thealgorithms/ciphers/VigenereTest.java @@ -1,6 +1,7 @@ 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; @@ -9,28 +10,71 @@ class VigenereTest { Vigenere vigenere = new Vigenere(); @Test - void vigenereEncryptTest() { - // given + void testVigenereEncryptDecrypt() { String text = "Hello World!"; String key = "suchsecret"; - // when - String cipherText = vigenere.encrypt(text, key); + String encryptedText = vigenere.encrypt(text, key); + String decryptedText = vigenere.decrypt(encryptedText, key); - // then - assertEquals("Zynsg Yfvev!", cipherText); + assertEquals("Zynsg Aqipw!", encryptedText); + assertEquals("Hello World!", decryptedText); } @Test - void vigenereDecryptTest() { - // given - String encryptedText = "Zynsg Yfvev!"; - String key = "suchsecret"; + void testWithEmptyMessage() { + String text = ""; + String key = "anykey"; - // when + String encryptedText = vigenere.encrypt(text, key); String decryptedText = vigenere.decrypt(encryptedText, key); - // then - assertEquals("Hello World!", decryptedText); + assertEquals("", encryptedText); + assertEquals("", decryptedText); + } + + @Test + void testWithEmptyKey() { + String text = "This should remain the same"; + String key = ""; + + assertThrows(IllegalArgumentException.class, () -> vigenere.encrypt(text, key)); + assertThrows(IllegalArgumentException.class, () -> vigenere.decrypt(text, key)); + } + + @Test + void testWithNumbersInMessage() { + String text = "Vigenere123!"; + String key = "cipher"; + + String encryptedText = vigenere.encrypt(text, key); + String decryptedText = vigenere.decrypt(encryptedText, key); + + assertEquals("Xqvlrvtm123!", encryptedText); + assertEquals(text, decryptedText); + } + + @Test + void testLongerKeyThanMessage() { + String text = "Short"; + String key = "VeryLongSecretKey"; + + String encryptedText = vigenere.encrypt(text, key); + String decryptedText = vigenere.decrypt(encryptedText, key); + + assertEquals("Nlfpe", encryptedText); + assertEquals(text, decryptedText); + } + + @Test + void testUppercaseMessageAndKey() { + String text = "HELLO"; + String key = "SECRET"; + + String encryptedText = vigenere.encrypt(text, key); + String decryptedText = vigenere.decrypt(encryptedText, key); + + assertEquals("ZINCS", encryptedText); + assertEquals(text, decryptedText); } } diff --git a/src/test/java/com/thealgorithms/ciphers/XORCipherTest.java b/src/test/java/com/thealgorithms/ciphers/XORCipherTest.java index 15e27d5d6778..fdfe640cc19b 100644 --- a/src/test/java/com/thealgorithms/ciphers/XORCipherTest.java +++ b/src/test/java/com/thealgorithms/ciphers/XORCipherTest.java @@ -1,34 +1,85 @@ package com.thealgorithms.ciphers; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; class XORCipherTest { @Test - void xorEncryptTest() { - // given + void xorEncryptDecryptTest() { String plaintext = "My t&xt th@t will be ençrypted..."; String key = "My ç&cret key!"; - // when String cipherText = XORCipher.encrypt(plaintext, key); + String decryptedText = XORCipher.decrypt(cipherText, key); - // then - assertEquals("000000b7815e1752111c601f450e48211500a1c206061ca6d35212150d4429570eed", cipherText); + assertEquals("My t&xt th@t will be ençrypted...", decryptedText); } @Test - void xorDecryptTest() { - // given - String cipherText = "000000b7815e1752111c601f450e48211500a1c206061ca6d35212150d4429570eed"; - String key = "My ç&cret key!"; + void testEmptyPlaintext() { + String plaintext = ""; + String key = "anykey"; + + String cipherText = XORCipher.encrypt(plaintext, key); + String decryptedText = XORCipher.decrypt(cipherText, key); + + assertEquals("", cipherText); + assertEquals("", decryptedText); + } + + @Test + void testEmptyKey() { + String plaintext = "Hello World!"; + String key = ""; + + assertThrows(IllegalArgumentException.class, () -> XORCipher.encrypt(plaintext, key)); + assertThrows(IllegalArgumentException.class, () -> XORCipher.decrypt(plaintext, key)); + } + + @Test + void testShortKey() { + String plaintext = "Short message"; + String key = "k"; + + String cipherText = XORCipher.encrypt(plaintext, key); + String decryptedText = XORCipher.decrypt(cipherText, key); + + assertEquals(plaintext, decryptedText); + } - // when - String plainText = XORCipher.decrypt(cipherText, key); + @Test + void testNonASCIICharacters() { + String plaintext = "こんにちは世界"; // "Hello World" in Japanese (Konichiwa Sekai) + String key = "key"; + + String cipherText = XORCipher.encrypt(plaintext, key); + String decryptedText = XORCipher.decrypt(cipherText, key); + + assertEquals(plaintext, decryptedText); + } + + @Test + void testSameKeyAndPlaintext() { + String plaintext = "samekey"; + String key = "samekey"; + + String cipherText = XORCipher.encrypt(plaintext, key); + String decryptedText = XORCipher.decrypt(cipherText, key); + + assertEquals(plaintext, decryptedText); + } + + @Test + void testLongPlaintextShortKey() { + String plaintext = "This is a long plaintext message."; + String key = "key"; + + String cipherText = XORCipher.encrypt(plaintext, key); + String decryptedText = XORCipher.decrypt(cipherText, key); - // then - assertEquals("My t&xt th@t will be ençrypted...", plainText); + assertEquals(plaintext, decryptedText); } } diff --git a/src/test/java/com/thealgorithms/ciphers/a5/A5CipherTest.java b/src/test/java/com/thealgorithms/ciphers/a5/A5CipherTest.java index aa725b644a86..011a1b521e31 100644 --- a/src/test/java/com/thealgorithms/ciphers/a5/A5CipherTest.java +++ b/src/test/java/com/thealgorithms/ciphers/a5/A5CipherTest.java @@ -9,14 +9,12 @@ public class A5CipherTest { private A5Cipher a5Cipher; - private BitSet sessionKey; - private BitSet frameCounter; @BeforeEach void setUp() { // Initialize the session key and frame counter - sessionKey = BitSet.valueOf(new long[] {0b1010101010101010L}); - frameCounter = BitSet.valueOf(new long[] {0b0000000000000001L}); + final var sessionKey = BitSet.valueOf(new long[] {0b1010101010101010L}); + final var frameCounter = BitSet.valueOf(new long[] {0b0000000000000001L}); a5Cipher = new A5Cipher(sessionKey, frameCounter); } diff --git a/src/test/java/com/thealgorithms/ciphers/a5/A5KeyStreamGeneratorTest.java b/src/test/java/com/thealgorithms/ciphers/a5/A5KeyStreamGeneratorTest.java index bb18d4500fc0..c9b721f90b2d 100644 --- a/src/test/java/com/thealgorithms/ciphers/a5/A5KeyStreamGeneratorTest.java +++ b/src/test/java/com/thealgorithms/ciphers/a5/A5KeyStreamGeneratorTest.java @@ -12,7 +12,6 @@ public class A5KeyStreamGeneratorTest { private A5KeyStreamGenerator keyStreamGenerator; - private BitSet sessionKey; private BitSet frameCounter; @BeforeEach @@ -20,7 +19,7 @@ void setUp() { keyStreamGenerator = new A5KeyStreamGenerator(); // Initialize session key and frame counter for testing - sessionKey = BitSet.valueOf(new long[] {0b1010101010101010L}); // Example 16-bit key + final var sessionKey = BitSet.valueOf(new long[] {0b1010101010101010L}); // Example 16-bit key frameCounter = BitSet.valueOf(new long[] {0b0000000000000001L}); // Example 16-bit frame counter keyStreamGenerator.initialize(sessionKey, frameCounter); } diff --git a/src/test/java/com/thealgorithms/conversions/AffineConverterTest.java b/src/test/java/com/thealgorithms/conversions/AffineConverterTest.java index 2705955f68f6..47eea139f424 100644 --- a/src/test/java/com/thealgorithms/conversions/AffineConverterTest.java +++ b/src/test/java/com/thealgorithms/conversions/AffineConverterTest.java @@ -16,31 +16,39 @@ void setUp() { } @Test - void testConstructor() { + void testConstructorWithValidValues() { assertEquals(3.0, converter.convert(0.0), "Expected value when input is 0.0"); assertEquals(5.0, converter.convert(1.0), "Expected value when input is 1.0"); - assertEquals(7.0, converter.convert(2.0), "Expected value when input is 2.0"); } @Test - void testConvert() { - assertEquals(3.0, converter.convert(0.0), "Conversion at 0.0 should equal the intercept"); - assertEquals(7.0, converter.convert(2.0), "2.0 should convert to 7.0"); - assertEquals(11.0, converter.convert(4.0), "4.0 should convert to 11.0"); + void testConstructorWithInvalidValues() { + assertThrows(IllegalArgumentException.class, () -> new AffineConverter(Double.NaN, 3.0), "Constructor should throw IllegalArgumentException for NaN slope"); + } + + @Test + void testConvertWithNegativeValues() { + assertEquals(-1.0, converter.convert(-2.0), "Negative input should convert correctly"); + assertEquals(-3.0, new AffineConverter(-1.0, -1.0).convert(2.0), "Slope and intercept can be negative"); + } + + @Test + void testConvertWithFloatingPointPrecision() { + double result = new AffineConverter(1.3333, 0.6667).convert(3.0); + assertEquals(4.6666, result, 1e-4, "Conversion should maintain floating-point precision"); } @Test void testInvert() { AffineConverter inverted = converter.invert(); - assertEquals(0.0, inverted.convert(3.0), "Inverted converter should return 0.0 for input 3.0"); - assertEquals(1.0, inverted.convert(5.0), "Inverted converter should return 1.0 for input 5.0"); - assertEquals(2.0, inverted.convert(7.0), "Inverted converter should return 2.0 for input 7.0"); + assertEquals(0.0, inverted.convert(3.0), "Inverted should return 0.0 for input 3.0"); + assertEquals(1.0, inverted.convert(5.0), "Inverted should return 1.0 for input 5.0"); } @Test void testInvertWithZeroSlope() { AffineConverter zeroSlopeConverter = new AffineConverter(0.0, 3.0); - assertThrows(AssertionError.class, zeroSlopeConverter::invert, "Invert should throw assertion error when slope is zero"); + assertThrows(AssertionError.class, zeroSlopeConverter::invert, "Invert should throw AssertionError when slope is zero"); } @Test @@ -50,6 +58,30 @@ void testCompose() { assertEquals(7.0, composed.convert(0.0), "Expected composed conversion at 0.0"); assertEquals(9.0, composed.convert(1.0), "Expected composed conversion at 1.0"); - assertEquals(11.0, composed.convert(2.0), "Expected composed conversion at 2.0"); + } + + @Test + void testMultipleCompositions() { + AffineConverter c1 = new AffineConverter(2.0, 1.0); + AffineConverter c2 = new AffineConverter(3.0, -2.0); + AffineConverter c3 = c1.compose(c2); // (2x + 1) ∘ (3x - 2) => 6x - 1 + + assertEquals(-3.0, c3.convert(0.0), "Composed transformation should return -3.0 at 0.0"); + assertEquals(3.0, c3.convert(1.0), "Composed transformation should return 3.0 at 1.0"); + } + + @Test + void testIdentityComposition() { + AffineConverter identity = new AffineConverter(1.0, 0.0); + AffineConverter composed = converter.compose(identity); + + assertEquals(3.0, composed.convert(0.0), "Identity composition should not change the transformation"); + assertEquals(7.0, composed.convert(2.0), "Identity composition should behave like the original"); + } + + @Test + void testLargeInputs() { + double largeValue = 1e6; + assertEquals(2.0 * largeValue + 3.0, converter.convert(largeValue), "Should handle large input values without overflow"); } } diff --git a/src/test/java/com/thealgorithms/conversions/AnytoAnyTest.java b/src/test/java/com/thealgorithms/conversions/AnytoAnyTest.java new file mode 100644 index 000000000000..cdc012180bf9 --- /dev/null +++ b/src/test/java/com/thealgorithms/conversions/AnytoAnyTest.java @@ -0,0 +1,48 @@ +package com.thealgorithms.conversions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class AnytoAnyTest { + + @Test + void testValidConversions() { + assertEquals(101, AnytoAny.convertBase(5, 10, 2), "Decimal 5 should convert to binary 101"); + assertEquals(2, AnytoAny.convertBase(2, 2, 10), "Binary 10 should convert to decimal 2"); + assertEquals(6, AnytoAny.convertBase(110, 2, 8), "Binary 110 should convert to octal 6"); + assertEquals(111, AnytoAny.convertBase(7, 10, 2), "Decimal 7 should convert to binary 111"); + } + + @Test + void testDecimalToBinary() { + assertEquals(1101, AnytoAny.convertBase(13, 10, 2), "Decimal 13 should convert to binary 1101"); + assertEquals(0, AnytoAny.convertBase(0, 10, 2), "Decimal 0 should convert to binary 0"); + } + + @Test + void testBinaryToDecimal() { + assertEquals(13, AnytoAny.convertBase(1101, 2, 10), "Binary 1101 should convert to decimal 13"); + assertEquals(0, AnytoAny.convertBase(0, 2, 10), "Binary 0 should convert to decimal 0"); + } + + @Test + void testOctalToDecimal() { + assertEquals(8, AnytoAny.convertBase(10, 8, 10), "Octal 10 should convert to decimal 8"); + assertEquals(65, AnytoAny.convertBase(101, 8, 10), "Octal 101 should convert to decimal 65"); + } + + @Test + void testInvalidBases() { + assertThrows(IllegalArgumentException.class, () -> AnytoAny.convertBase(5, 1, 10), "Source base less than 2 should throw IllegalArgumentException"); + + assertThrows(IllegalArgumentException.class, () -> AnytoAny.convertBase(5, 10, 11), "Destination base greater than 10 should throw IllegalArgumentException"); + } + + @Test + void testLargeNumberConversion() { + assertEquals(1111101000, AnytoAny.convertBase(1000, 10, 2), "Decimal 1000 should convert to binary 1111101000"); + assertEquals(1750, AnytoAny.convertBase(1000, 10, 8), "Decimal 1000 should convert to octal 1750"); + } +} diff --git a/src/test/java/com/thealgorithms/conversions/EndianConverterTest.java b/src/test/java/com/thealgorithms/conversions/EndianConverterTest.java new file mode 100644 index 000000000000..85ffa2190962 --- /dev/null +++ b/src/test/java/com/thealgorithms/conversions/EndianConverterTest.java @@ -0,0 +1,35 @@ +package com.thealgorithms.conversions; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +public class EndianConverterTest { + + @ParameterizedTest + @CsvSource({ + "0x78563412, 0x12345678", "0x00000000, 0x00000000", "0x00000001, 0x01000000", + "0xFFFFFFFF, 0xFFFFFFFF", // -1 in two's complement + "0x0000007F, 0x7F000000" // Positive boundary case + }) + public void + testLittleToBigEndian(String inputHex, String expectedHex) { + int input = (int) Long.parseLong(inputHex.substring(2), 16); // Convert hex string to int + int expected = (int) Long.parseLong(expectedHex.substring(2), 16); // Convert hex string to int + assertEquals(expected, EndianConverter.littleToBigEndian(input)); + } + + @ParameterizedTest + @CsvSource({ + "0x12345678, 0x78563412", "0x00000000, 0x00000000", "0x01000000, 0x00000001", + "0xFFFFFFFF, 0xFFFFFFFF", // -1 in two's complement + "0x7F000000, 0x0000007F" // Positive boundary case + }) + public void + testBigToLittleEndian(String inputHex, String expectedHex) { + int input = (int) Long.parseLong(inputHex.substring(2), 16); // Convert hex string to int + int expected = (int) Long.parseLong(expectedHex.substring(2), 16); // Convert hex string to int + assertEquals(expected, EndianConverter.bigToLittleEndian(input)); + } +} diff --git a/src/test/java/com/thealgorithms/conversions/HexaDecimalToBinaryTest.java b/src/test/java/com/thealgorithms/conversions/HexaDecimalToBinaryTest.java index 72a0a0174a93..1426eab64d2c 100644 --- a/src/test/java/com/thealgorithms/conversions/HexaDecimalToBinaryTest.java +++ b/src/test/java/com/thealgorithms/conversions/HexaDecimalToBinaryTest.java @@ -2,14 +2,44 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +/** + * Unit tests for the {@link EndianConverter} class. + */ public class HexaDecimalToBinaryTest { - @Test - public void testHexaDecimalToBinary() { - HexaDecimalToBinary hexaDecimalToBinary = new HexaDecimalToBinary(); - assertEquals("1111111111111111111111111111111", hexaDecimalToBinary.convert("7fffffff")); - assertEquals("101010111100110111101111", hexaDecimalToBinary.convert("abcdef")); + /** + * Parameterized test to validate the conversion from little-endian to big-endian. + * Hexadecimal values are passed as strings and converted to integers during the test. + */ + @ParameterizedTest + @CsvSource({ + "0x78563412, 0x12345678", "0x00000000, 0x00000000", "0x00000001, 0x01000000", + "0xFFFFFFFF, 0xFFFFFFFF", // -1 in two's complement + "0x0000007F, 0x7F000000" // Positive boundary case + }) + public void + testLittleToBigEndian(String inputHex, String expectedHex) { + int input = (int) Long.parseLong(inputHex.substring(2), 16); // Convert hex string to int + int expected = (int) Long.parseLong(expectedHex.substring(2), 16); // Convert hex string to int + assertEquals(expected, EndianConverter.littleToBigEndian(input)); + } + + /** + * Parameterized test to validate the conversion from big-endian to little-endian. + */ + @ParameterizedTest + @CsvSource({ + "0x12345678, 0x78563412", "0x00000000, 0x00000000", "0x01000000, 0x00000001", + "0xFFFFFFFF, 0xFFFFFFFF", // -1 in two's complement + "0x7F000000, 0x0000007F" // Positive boundary case + }) + public void + testBigToLittleEndian(String inputHex, String expectedHex) { + int input = (int) Long.parseLong(inputHex.substring(2), 16); // Convert hex string to int + int expected = (int) Long.parseLong(expectedHex.substring(2), 16); // Convert hex string to int + assertEquals(expected, EndianConverter.bigToLittleEndian(input)); } } diff --git a/src/test/java/com/thealgorithms/conversions/HexaDecimalToDecimalTest.java b/src/test/java/com/thealgorithms/conversions/HexaDecimalToDecimalTest.java index c9c2ab2161ed..d0d6b400e299 100644 --- a/src/test/java/com/thealgorithms/conversions/HexaDecimalToDecimalTest.java +++ b/src/test/java/com/thealgorithms/conversions/HexaDecimalToDecimalTest.java @@ -1,14 +1,37 @@ package com.thealgorithms.conversions; 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 HexaDecimalToDecimalTest { - @Test - public void testhexaDecimalToDecimal() { - assertEquals(161, HexaDecimalToDecimal.getHexaToDec("A1")); - assertEquals(428, HexaDecimalToDecimal.getHexaToDec("1ac")); + @ParameterizedTest + @CsvSource({ + "A1, 161", // Simple case with two characters + "1AC, 428", // Mixed-case input + "0, 0", // Single zero + "F, 15", // Single digit + "10, 16", // Power of 16 + "FFFF, 65535", // Max 4-character hex + "7FFFFFFF, 2147483647" // Max positive int value + }) + public void + testValidHexaToDecimal(String hexInput, int expectedDecimal) { + assertEquals(expectedDecimal, HexaDecimalToDecimal.getHexaToDec(hexInput)); + } + + @ParameterizedTest + @CsvSource({ + "G", // Invalid character + "1Z", // Mixed invalid input + "123G", // Valid prefix with invalid character + "#$%" // Non-hexadecimal symbols + }) + public void + testInvalidHexaToDecimal(String invalidHex) { + assertThrows(IllegalArgumentException.class, () -> HexaDecimalToDecimal.getHexaToDec(invalidHex)); } } diff --git a/src/test/java/com/thealgorithms/conversions/IPConverterTest.java b/src/test/java/com/thealgorithms/conversions/IPConverterTest.java new file mode 100644 index 000000000000..78c226a9237b --- /dev/null +++ b/src/test/java/com/thealgorithms/conversions/IPConverterTest.java @@ -0,0 +1,30 @@ +package com.thealgorithms.conversions; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class IPConverterTest { + + private static String generateTestIP(int a, int b, int c, int d) { + return String.format("%d.%d.%d.%d", a, b, c, d); + } + + private static String generateTestBinary(int a, int b, int c, int d) { + return String.format("%8s.%8s.%8s.%8s", Integer.toBinaryString(a), Integer.toBinaryString(b), Integer.toBinaryString(c), Integer.toBinaryString(d)).replace(' ', '0'); + } + + @Test + public void testIpToBinary() { + assertEquals(generateTestBinary(192, 168, 1, 1), IPConverter.ipToBinary(generateTestIP(192, 168, 1, 1))); + assertEquals(generateTestBinary(127, 3, 4, 5), IPConverter.ipToBinary(generateTestIP(127, 3, 4, 5))); + assertEquals(generateTestBinary(0, 0, 0, 0), IPConverter.ipToBinary(generateTestIP(0, 0, 0, 0))); + } + + @Test + public void testBinaryToIP() { + assertEquals(generateTestIP(192, 168, 1, 1), IPConverter.binaryToIP(generateTestBinary(192, 168, 1, 1))); + assertEquals(generateTestIP(127, 3, 4, 5), IPConverter.binaryToIP(generateTestBinary(127, 3, 4, 5))); + assertEquals(generateTestIP(0, 0, 0, 0), IPConverter.binaryToIP(generateTestBinary(0, 0, 0, 0))); + } +} diff --git a/src/test/java/com/thealgorithms/conversions/IPv6ConverterTest.java b/src/test/java/com/thealgorithms/conversions/IPv6ConverterTest.java new file mode 100644 index 000000000000..443f865ae0dc --- /dev/null +++ b/src/test/java/com/thealgorithms/conversions/IPv6ConverterTest.java @@ -0,0 +1,64 @@ +package com.thealgorithms.conversions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.net.UnknownHostException; +import org.junit.jupiter.api.Test; + +public class IPv6ConverterTest { + + private static final String VALID_IPV4 = "192." + + "0." + + "2." + + "128"; + private static final String EXPECTED_IPV6_MAPPED = ":" + + ":ff" + + "ff" + + ":19" + + "2." + + "0." + + "2.128"; + private static final String INVALID_IPV6_MAPPED = "2001:" + + "db8" + + ":" + + ":1"; + private static final String INVALID_IPV4 = "999." + + "999." + + "999." + + "999"; + private static final String INVALID_IPV6_FORMAT = "invalid:ipv6" + + "::address"; + private static final String EMPTY_STRING = ""; + + @Test + public void testIpv4ToIpv6ValidInput() throws UnknownHostException { + String actualIpv6 = IPv6Converter.ipv4ToIpv6(VALID_IPV4); + assertEquals(EXPECTED_IPV6_MAPPED, actualIpv6); + } + + @Test + public void testIpv6ToIpv4InvalidIPv6MappedAddress() { + assertThrows(IllegalArgumentException.class, () -> IPv6Converter.ipv6ToIpv4(INVALID_IPV6_MAPPED)); + } + + @Test + public void testIpv4ToIpv6InvalidIPv4Address() { + assertThrows(UnknownHostException.class, () -> IPv6Converter.ipv4ToIpv6(INVALID_IPV4)); + } + + @Test + public void testIpv6ToIpv4InvalidFormat() { + assertThrows(UnknownHostException.class, () -> IPv6Converter.ipv6ToIpv4(INVALID_IPV6_FORMAT)); + } + + @Test + public void testIpv4ToIpv6EmptyString() { + assertThrows(UnknownHostException.class, () -> IPv6Converter.ipv4ToIpv6(EMPTY_STRING)); + } + + @Test + public void testIpv6ToIpv4EmptyString() { + assertThrows(IllegalArgumentException.class, () -> IPv6Converter.ipv6ToIpv4(EMPTY_STRING)); + } +} diff --git a/src/test/java/com/thealgorithms/conversions/IntegerToEnglishTest.java b/src/test/java/com/thealgorithms/conversions/IntegerToEnglishTest.java index 49c43402aeca..c2a94794b7f8 100644 --- a/src/test/java/com/thealgorithms/conversions/IntegerToEnglishTest.java +++ b/src/test/java/com/thealgorithms/conversions/IntegerToEnglishTest.java @@ -11,5 +11,36 @@ public void testIntegerToEnglish() { assertEquals("Two Billion One Hundred Forty Seven Million Four Hundred Eighty Three Thousand Six Hundred Forty Seven", IntegerToEnglish.integerToEnglishWords(2147483647)); assertEquals("One Million Two Hundred Thirty Four Thousand Five Hundred Sixty Seven", IntegerToEnglish.integerToEnglishWords(1234567)); assertEquals("Twelve Thousand Three Hundred Forty Five", IntegerToEnglish.integerToEnglishWords(12345)); + assertEquals("One Hundred", IntegerToEnglish.integerToEnglishWords(100)); + assertEquals("Zero", IntegerToEnglish.integerToEnglishWords(0)); + } + + @Test + public void testSmallNumbers() { + assertEquals("Ten", IntegerToEnglish.integerToEnglishWords(10)); + assertEquals("Nineteen", IntegerToEnglish.integerToEnglishWords(19)); + assertEquals("Twenty One", IntegerToEnglish.integerToEnglishWords(21)); + assertEquals("Ninety Nine", IntegerToEnglish.integerToEnglishWords(99)); + } + + @Test + public void testHundreds() { + assertEquals("One Hundred One", IntegerToEnglish.integerToEnglishWords(101)); + assertEquals("Five Hundred Fifty", IntegerToEnglish.integerToEnglishWords(550)); + assertEquals("Nine Hundred Ninety Nine", IntegerToEnglish.integerToEnglishWords(999)); + } + + @Test + public void testThousands() { + assertEquals("One Thousand", IntegerToEnglish.integerToEnglishWords(1000)); + assertEquals("Ten Thousand One", IntegerToEnglish.integerToEnglishWords(10001)); + assertEquals("Seventy Six Thousand Five Hundred Forty Three", IntegerToEnglish.integerToEnglishWords(76543)); + } + + @Test + public void testEdgeCases() { + assertEquals("One Million", IntegerToEnglish.integerToEnglishWords(1_000_000)); + assertEquals("One Billion", IntegerToEnglish.integerToEnglishWords(1_000_000_000)); + assertEquals("Two Thousand", IntegerToEnglish.integerToEnglishWords(2000)); } } diff --git a/src/test/java/com/thealgorithms/conversions/IntegerToRomanTest.java b/src/test/java/com/thealgorithms/conversions/IntegerToRomanTest.java index 04768d034b93..181cad930282 100644 --- a/src/test/java/com/thealgorithms/conversions/IntegerToRomanTest.java +++ b/src/test/java/com/thealgorithms/conversions/IntegerToRomanTest.java @@ -10,5 +10,30 @@ public class IntegerToRomanTest { public void testIntegerToRoman() { assertEquals("MCMXCIV", IntegerToRoman.integerToRoman(1994)); assertEquals("LVIII", IntegerToRoman.integerToRoman(58)); + assertEquals("IV", IntegerToRoman.integerToRoman(4)); + assertEquals("IX", IntegerToRoman.integerToRoman(9)); + assertEquals("MMM", IntegerToRoman.integerToRoman(3000)); + } + + @Test + public void testSmallNumbers() { + assertEquals("I", IntegerToRoman.integerToRoman(1)); + assertEquals("II", IntegerToRoman.integerToRoman(2)); + assertEquals("III", IntegerToRoman.integerToRoman(3)); + } + + @Test + public void testRoundNumbers() { + assertEquals("X", IntegerToRoman.integerToRoman(10)); + assertEquals("L", IntegerToRoman.integerToRoman(50)); + assertEquals("C", IntegerToRoman.integerToRoman(100)); + assertEquals("D", IntegerToRoman.integerToRoman(500)); + assertEquals("M", IntegerToRoman.integerToRoman(1000)); + } + + @Test + public void testEdgeCases() { + assertEquals("", IntegerToRoman.integerToRoman(0)); // Non-positive number case + assertEquals("", IntegerToRoman.integerToRoman(-5)); // Negative number case } } diff --git a/src/test/java/com/thealgorithms/conversions/MorseCodeConverterTest.java b/src/test/java/com/thealgorithms/conversions/MorseCodeConverterTest.java new file mode 100644 index 000000000000..153b632a0510 --- /dev/null +++ b/src/test/java/com/thealgorithms/conversions/MorseCodeConverterTest.java @@ -0,0 +1,19 @@ +package com.thealgorithms.conversions; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class MorseCodeConverterTest { + + @Test + public void testTextToMorse() { + assertEquals(".- -...", MorseCodeConverter.textToMorse("AB")); + assertEquals(".... . .-.. .-.. --- | .-- --- .-. .-.. -..", MorseCodeConverter.textToMorse("HELLO WORLD")); + } + + @Test + public void testMorseToText() { + assertEquals("AB", MorseCodeConverter.morseToText(".- -...")); + } +} diff --git a/src/test/java/com/thealgorithms/conversions/NumberToWordsTest.java b/src/test/java/com/thealgorithms/conversions/NumberToWordsTest.java new file mode 100644 index 000000000000..7b264678daa4 --- /dev/null +++ b/src/test/java/com/thealgorithms/conversions/NumberToWordsTest.java @@ -0,0 +1,60 @@ +package com.thealgorithms.conversions; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.math.BigDecimal; +import org.junit.jupiter.api.Test; + +public class NumberToWordsTest { + + @Test + void testNullInput() { + assertEquals("Invalid Input", NumberToWords.convert(null), "Null input should return 'Invalid Input'"); + } + + @Test + void testZeroInput() { + assertEquals("Zero", NumberToWords.convert(BigDecimal.ZERO), "Zero input should return 'Zero'"); + } + + @Test + void testPositiveWholeNumbers() { + assertEquals("One", NumberToWords.convert(BigDecimal.ONE), "1 should convert to 'One'"); + assertEquals("One Thousand", NumberToWords.convert(new BigDecimal("1000")), "1000 should convert to 'One Thousand'"); + assertEquals("One Million", NumberToWords.convert(new BigDecimal("1000000")), "1000000 should convert to 'One Million'"); + } + + @Test + void testNegativeWholeNumbers() { + assertEquals("Negative One", NumberToWords.convert(new BigDecimal("-1")), "-1 should convert to 'Negative One'"); + assertEquals("Negative One Thousand", NumberToWords.convert(new BigDecimal("-1000")), "-1000 should convert to 'Negative One Thousand'"); + } + + @Test + void testFractionalNumbers() { + assertEquals("Zero Point One Two Three", NumberToWords.convert(new BigDecimal("0.123")), "0.123 should convert to 'Zero Point One Two Three'"); + assertEquals("Negative Zero Point Four Five Six", NumberToWords.convert(new BigDecimal("-0.456")), "-0.456 should convert to 'Negative Zero Point Four Five Six'"); + } + + @Test + void testLargeNumbers() { + assertEquals("Nine Hundred Ninety Nine Million Nine Hundred Ninety Nine Thousand Nine Hundred Ninety Nine", NumberToWords.convert(new BigDecimal("999999999")), "999999999 should convert correctly"); + assertEquals("One Trillion", NumberToWords.convert(new BigDecimal("1000000000000")), "1000000000000 should convert to 'One Trillion'"); + } + + @Test + void testNegativeLargeNumbers() { + assertEquals("Negative Nine Trillion Eight Hundred Seventy Six Billion Five Hundred Forty Three Million Two Hundred Ten Thousand Nine Hundred Eighty Seven", NumberToWords.convert(new BigDecimal("-9876543210987")), "-9876543210987 should convert correctly"); + } + + @Test + void testFloatingPointPrecision() { + assertEquals("One Million Point Zero Zero One", NumberToWords.convert(new BigDecimal("1000000.001")), "1000000.001 should convert to 'One Million Point Zero Zero One'"); + } + + @Test + void testEdgeCases() { + assertEquals("Zero", NumberToWords.convert(new BigDecimal("-0.0")), "-0.0 should convert to 'Zero'"); + assertEquals("Zero Point Zero Zero Zero Zero Zero Zero One", NumberToWords.convert(new BigDecimal("1E-7")), "1E-7 should convert to 'Zero Point Zero Zero Zero Zero Zero Zero One'"); + } +} diff --git a/src/test/java/com/thealgorithms/conversions/OctalToBinaryTest.java b/src/test/java/com/thealgorithms/conversions/OctalToBinaryTest.java index 86cf692c5258..8e007151c301 100644 --- a/src/test/java/com/thealgorithms/conversions/OctalToBinaryTest.java +++ b/src/test/java/com/thealgorithms/conversions/OctalToBinaryTest.java @@ -12,4 +12,24 @@ public void testConvertOctalToBinary() { assertEquals(101010, OctalToBinary.convertOctalToBinary(52)); assertEquals(110, OctalToBinary.convertOctalToBinary(6)); } + + @Test + public void testConvertOctalToBinarySingleDigit() { + assertEquals(0, OctalToBinary.convertOctalToBinary(0)); + assertEquals(1, OctalToBinary.convertOctalToBinary(1)); + assertEquals(111, OctalToBinary.convertOctalToBinary(7)); + } + + @Test + public void testConvertOctalToBinaryMultipleDigits() { + assertEquals(100110111, OctalToBinary.convertOctalToBinary(467)); + assertEquals(111101, OctalToBinary.convertOctalToBinary(75)); + assertEquals(111100101, OctalToBinary.convertOctalToBinary(745)); + } + + @Test + public void testConvertOctalToBinaryWithZeroPadding() { + assertEquals(100001010, OctalToBinary.convertOctalToBinary(412)); + assertEquals(101101110, OctalToBinary.convertOctalToBinary(556)); + } } diff --git a/src/test/java/com/thealgorithms/conversions/PhoneticAlphabetConverterTest.java b/src/test/java/com/thealgorithms/conversions/PhoneticAlphabetConverterTest.java new file mode 100644 index 000000000000..360af7fa0f51 --- /dev/null +++ b/src/test/java/com/thealgorithms/conversions/PhoneticAlphabetConverterTest.java @@ -0,0 +1,27 @@ +package com.thealgorithms.conversions; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +public class PhoneticAlphabetConverterTest { + + @ParameterizedTest + @CsvSource({ + "'AB', 'Alpha Bravo'", "'ABC', 'Alpha Bravo Charlie'", "'A1B2C3', 'Alpha One Bravo Two Charlie Three'", "'Hello', 'Hotel Echo Lima Lima Oscar'", "'123', 'One Two Three'", + "'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 'Alpha Bravo Charlie Delta Echo Foxtrot Golf Hotel India Juliett Kilo Lima Mike November Oscar Papa Quebec Romeo Sierra Tango Uniform Victor Whiskey X-ray Yankee Zulu Zero One Two Three Four Five Six Seven Eight Nine'", + "'abcdefghijklmnopqrstuvwxyz0123456789', 'Alpha Bravo Charlie Delta Echo Foxtrot Golf Hotel India Juliett Kilo Lima Mike November Oscar Papa Quebec Romeo Sierra Tango Uniform Victor Whiskey X-ray Yankee Zulu Zero One Two Three Four Five Six Seven Eight Nine'", + "'', ''", // Empty string case + "'A B C', 'Alpha Bravo Charlie'", // String with spaces + "'A@B#C', 'Alpha @ Bravo # Charlie'", // Special characters + "'A B C 123', 'Alpha Bravo Charlie One Two Three'", // Mixed letters, digits, and spaces + "'a b c', 'Alpha Bravo Charlie'", // Lowercase letters with spaces + "'123!@#', 'One Two Three ! @ #'", // Numbers with special characters + "'HELLO WORLD', 'Hotel Echo Lima Lima Oscar Whiskey Oscar Romeo Lima Delta'" // Words with space + }) + public void + testTextToPhonetic(String input, String expectedOutput) { + assertEquals(expectedOutput, PhoneticAlphabetConverter.textToPhonetic(input)); + } +} diff --git a/src/test/java/com/thealgorithms/conversions/RomanToIntegerTest.java b/src/test/java/com/thealgorithms/conversions/RomanToIntegerTest.java index f03563971cb7..971eff8c74f5 100644 --- a/src/test/java/com/thealgorithms/conversions/RomanToIntegerTest.java +++ b/src/test/java/com/thealgorithms/conversions/RomanToIntegerTest.java @@ -8,16 +8,31 @@ public class RomanToIntegerTest { @Test - public void testRomanToInteger() { + public void testValidRomanToInteger() { assertEquals(1994, RomanToInteger.romanToInt("MCMXCIV")); assertEquals(58, RomanToInteger.romanToInt("LVIII")); assertEquals(1804, RomanToInteger.romanToInt("MDCCCIV")); + assertEquals(9, RomanToInteger.romanToInt("IX")); + assertEquals(4, RomanToInteger.romanToInt("IV")); + assertEquals(3000, RomanToInteger.romanToInt("MMM")); } @Test - void testRomanToIntegerThrows() { + public void testLowercaseInput() { + assertEquals(1994, RomanToInteger.romanToInt("mcmxciv")); + assertEquals(58, RomanToInteger.romanToInt("lviii")); + } + + @Test + public void testInvalidRomanNumerals() { assertThrows(IllegalArgumentException.class, () -> RomanToInteger.romanToInt("Z")); assertThrows(IllegalArgumentException.class, () -> RomanToInteger.romanToInt("MZI")); assertThrows(IllegalArgumentException.class, () -> RomanToInteger.romanToInt("MMMO")); } + + @Test + public void testEmptyAndNullInput() { + assertEquals(0, RomanToInteger.romanToInt("")); // Empty string case + assertThrows(NullPointerException.class, () -> RomanToInteger.romanToInt(null)); // Null input case + } } diff --git a/src/test/java/com/thealgorithms/conversions/TurkishToLatinConversionTest.java b/src/test/java/com/thealgorithms/conversions/TurkishToLatinConversionTest.java new file mode 100644 index 000000000000..87e40c78e6a2 --- /dev/null +++ b/src/test/java/com/thealgorithms/conversions/TurkishToLatinConversionTest.java @@ -0,0 +1,31 @@ +package com.thealgorithms.conversions; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +public class TurkishToLatinConversionTest { + + @ParameterizedTest + @CsvSource({ + "'çalışma', 'calisma'", // Turkish to Latin conversion for lowercase + "'ÇALIŞMA', 'CALISMA'", // Turkish to Latin conversion for uppercase + "'İSTANBUL', 'ISTANBUL'", // Special case of 'İ' to 'I' + "'istanbul', 'istanbul'", // Special case of 'ı' to 'i' + "'GÜL', 'GUL'", // Special case of 'Ü' to 'U' + "'gül', 'gul'", // Special case of 'ü' to 'u' + "'ÖĞRENME', 'OGRENME'", // Special case of 'Ö' to 'O' and 'Ğ' to 'G' + "'öğrenme', 'ogrenme'", // Special case of 'ö' to 'o' and 'ğ' to 'g' + "'ŞEHIR', 'SEHIR'", // Special case of 'Ş' to 'S' + "'şehir', 'sehir'", // Special case of 'ş' to 's' + "'HELLO', 'HELLO'", // String with no Turkish characters, should remain unchanged + "'Merhaba Dünya!', 'Merhaba Dunya!'", // Mixed Turkish and Latin characters with punctuation + "'Çift kişilik yataklı odalar', 'Cift kisilik yatakli odalar'", // Full sentence conversion + "'', ''" // Empty string case + }) + public void + testConvertTurkishToLatin(String input, String expectedOutput) { + assertEquals(expectedOutput, TurkishToLatinConversion.convertTurkishToLatin(input)); + } +} diff --git a/src/test/java/com/thealgorithms/conversions/UnitConversionsTest.java b/src/test/java/com/thealgorithms/conversions/UnitConversionsTest.java index 073e7d6de2c6..3c4e3d5e4c54 100644 --- a/src/test/java/com/thealgorithms/conversions/UnitConversionsTest.java +++ b/src/test/java/com/thealgorithms/conversions/UnitConversionsTest.java @@ -13,8 +13,8 @@ public class UnitConversionsTest { private static void addData(Stream.Builder builder, Map values) { - for (final var first : values.entrySet()) { - for (final var second : values.entrySet()) { + for (var first : values.entrySet()) { + for (var second : values.entrySet()) { if (!first.getKey().equals(second.getKey())) { builder.add(Arguments.of(first.getKey(), second.getKey(), first.getValue(), second.getValue())); } diff --git a/src/test/java/com/thealgorithms/conversions/UnitsConverterTest.java b/src/test/java/com/thealgorithms/conversions/UnitsConverterTest.java index 580a66bc01ec..0952129efb4d 100644 --- a/src/test/java/com/thealgorithms/conversions/UnitsConverterTest.java +++ b/src/test/java/com/thealgorithms/conversions/UnitsConverterTest.java @@ -1,10 +1,12 @@ package com.thealgorithms.conversions; import static java.util.Map.entry; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Set; import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.Test; @@ -24,4 +26,19 @@ void testConvertThrowsForUnknownUnits() { assertThrows(NoSuchElementException.class, () -> someConverter.convert("X", "A", 20.0)); assertThrows(NoSuchElementException.class, () -> someConverter.convert("X", "Y", 20.0)); } + + @Test + void testAvailableUnits() { + final UnitsConverter someConverter = new UnitsConverter(Map.ofEntries(entry(Pair.of("Celsius", "Fahrenheit"), new AffineConverter(9.0 / 5.0, 32.0)), entry(Pair.of("Kelvin", "Celsius"), new AffineConverter(1.0, -273.15)))); + assertEquals(Set.of("Celsius", "Fahrenheit", "Kelvin"), someConverter.availableUnits()); + } + + @Test + void testInvertConversion() { + final UnitsConverter someConverter = new UnitsConverter(Map.ofEntries(entry(Pair.of("A", "B"), new AffineConverter(2.0, 5.0)))); + // Check conversion from A -> B + assertEquals(25.0, someConverter.convert("A", "B", 10.0), 0.0001); + // Check inverse conversion from B -> A + assertEquals(10.0, someConverter.convert("B", "A", 25.0), 0.0001); + } } diff --git a/src/test/java/com/thealgorithms/conversions/WordsToNumberTest.java b/src/test/java/com/thealgorithms/conversions/WordsToNumberTest.java new file mode 100644 index 000000000000..fbf63e37946b --- /dev/null +++ b/src/test/java/com/thealgorithms/conversions/WordsToNumberTest.java @@ -0,0 +1,114 @@ +package com.thealgorithms.conversions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.math.BigDecimal; +import org.junit.jupiter.api.Test; + +public class WordsToNumberTest { + + @Test + void testNullInput() { + WordsToNumberException exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert(null)); + assertEquals(WordsToNumberException.ErrorType.NULL_INPUT, exception.getErrorType(), "Exception should be of type NULL_INPUT"); + } + + @Test + void testStandardCases() { + assertEquals("0", WordsToNumber.convert("zero"), "'zero' should convert to '0'"); + assertEquals("5", WordsToNumber.convert("five"), "'five' should convert to '5'"); + assertEquals("21", WordsToNumber.convert("twenty one"), "'twenty one' should convert to '21'"); + assertEquals("101", WordsToNumber.convert("one hundred one"), "'one hundred' should convert to '101'"); + assertEquals("342", WordsToNumber.convert("three hundred and forty two"), "'three hundred and forty two' should convert to '342'"); + } + + @Test + void testLargeNumbers() { + assertEquals("1000000", WordsToNumber.convert("one million"), "'one million' should convert to '1000000'"); + assertEquals("1000000000", WordsToNumber.convert("one billion"), "'one billion' should convert to '1000000000'"); + assertEquals("1000000000000", WordsToNumber.convert("one trillion"), "'one trillion' should convert to '1000000000000'"); + assertEquals("999000000900999", WordsToNumber.convert("nine hundred ninety nine trillion nine hundred thousand nine hundred and ninety nine"), "'nine hundred ninety nine trillion nine hundred thousand nine hundred and ninety nine' should convert to '999000000900999'"); + } + + @Test + void testNegativeNumbers() { + assertEquals("-5", WordsToNumber.convert("negative five"), "'negative five' should convert to '-5'"); + assertEquals("-120", WordsToNumber.convert("negative one hundred and twenty"), "'negative one hundred and twenty' should convert correctly"); + } + + @Test + void testNegativeLargeNumbers() { + assertEquals("-1000000000000", WordsToNumber.convert("negative one trillion"), "'negative one trillion' should convert to '-1000000000000'"); + assertEquals("-9876543210987", WordsToNumber.convert("Negative Nine Trillion Eight Hundred Seventy Six Billion Five Hundred Forty Three Million Two Hundred Ten Thousand Nine Hundred Eighty Seven"), ""); + } + + @Test + void testDecimalNumbers() { + assertEquals("3.1415", WordsToNumber.convert("three point one four one five"), "'three point one four one five' should convert to '3.1415'"); + assertEquals("-2.718", WordsToNumber.convert("negative two point seven one eight"), "'negative two point seven one eight' should convert to '-2.718'"); + assertEquals("-1E-7", WordsToNumber.convert("negative zero point zero zero zero zero zero zero one"), "'negative zero point zero zero zero zero zero zero one' should convert to '-1E-7'"); + } + + @Test + void testLargeDecimalNumbers() { + assertEquals("1000000000.0000000001", WordsToNumber.convert("one billion point zero zero zero zero zero zero zero zero zero one"), "Tests a large whole number with a tiny fractional part"); + assertEquals("999999999999999.9999999999999", + WordsToNumber.convert("nine hundred ninety nine trillion nine hundred ninety nine billion nine hundred ninety nine million nine hundred ninety nine thousand nine hundred ninety nine point nine nine nine nine nine nine nine nine nine nine nine nine nine"), + "Tests maximum scale handling for large decimal numbers"); + assertEquals("0.505", WordsToNumber.convert("zero point five zero five"), "Tests a decimal with an internal zero, ensuring correct parsing"); + assertEquals("42.00000000000001", WordsToNumber.convert("forty two point zero zero zero zero zero zero zero zero zero zero zero zero zero one"), "Tests a decimal with leading zeros before a significant figure"); + assertEquals("7.89E-7", WordsToNumber.convert("zero point zero zero zero zero zero zero seven eight nine"), "Tests scientific notation for a small decimal with multiple digits"); + assertEquals("0.999999", WordsToNumber.convert("zero point nine nine nine nine nine nine"), "Tests a decimal close to one with multiple repeated digits"); + } + + @Test + void testCaseInsensitivity() { + assertEquals("21", WordsToNumber.convert("TWENTY-ONE"), "Uppercase should still convert correctly"); + assertEquals("-100.0001", WordsToNumber.convert("negAtiVe OnE HuNdReD, point ZeRO Zero zERo ONE"), "Mixed case should still convert correctly"); + assertEquals("-225647.00019", WordsToNumber.convert("nEgative twO HundRed, and twenty-Five thOusaNd, six huNdred Forty-Seven, Point zero zero zero One nInE")); + } + + @Test + void testInvalidInputs() { + WordsToNumberException exception; + + exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("negative one hundred AlPha")); + assertEquals(WordsToNumberException.ErrorType.UNKNOWN_WORD, exception.getErrorType()); + + exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("twenty thirteen")); + assertEquals(WordsToNumberException.ErrorType.UNEXPECTED_WORD, exception.getErrorType()); + + exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("negative negative ten")); + assertEquals(WordsToNumberException.ErrorType.MULTIPLE_NEGATIVES, exception.getErrorType()); + + exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("one hundred hundred")); + assertEquals(WordsToNumberException.ErrorType.UNEXPECTED_WORD, exception.getErrorType()); + + exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("one thousand and hundred")); + assertEquals(WordsToNumberException.ErrorType.INVALID_CONJUNCTION, exception.getErrorType()); + + exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("one thousand hundred")); + assertEquals(WordsToNumberException.ErrorType.UNEXPECTED_WORD, exception.getErrorType()); + + exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("nine hundred and nine hundred")); + assertEquals(WordsToNumberException.ErrorType.INVALID_CONJUNCTION, exception.getErrorType()); + + exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("forty two point")); + assertEquals(WordsToNumberException.ErrorType.MISSING_DECIMAL_NUMBERS, exception.getErrorType()); + + exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("sixty seven point hello")); + assertEquals(WordsToNumberException.ErrorType.UNEXPECTED_WORD_AFTER_POINT, exception.getErrorType()); + + exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("one negative")); + assertEquals(WordsToNumberException.ErrorType.INVALID_NEGATIVE, exception.getErrorType()); + } + + @Test + void testConvertToBigDecimal() { + assertEquals(new BigDecimal("-100000000000000.056"), WordsToNumber.convertToBigDecimal("negative one hundred trillion point zero five six"), "should convert to appropriate BigDecimal value"); + + WordsToNumberException exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convertToBigDecimal(null)); + assertEquals(WordsToNumberException.ErrorType.NULL_INPUT, exception.getErrorType(), "Exception should be of type NULL_INPUT"); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java b/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java index c0fe107bfba5..b7e64851383c 100644 --- a/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java +++ b/src/test/java/com/thealgorithms/datastructures/bag/BagTest.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.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import com.thealgorithms.datastructures.bags.Bag; @@ -68,12 +69,12 @@ void testContainsMethod() { } @Test - void testContainsAfterRemoveOperation() { + void testContainsAfterAdditions() { Bag bag = new Bag<>(); bag.add("item1"); bag.add("item2"); - assertTrue(bag.contains("item1"), "Bag should contain 'item1' before removal"); - assertTrue(bag.contains("item2"), "Bag should contain 'item2' before removal"); + assertTrue(bag.contains("item1"), "Bag should contain 'item1' after addition"); + assertTrue(bag.contains("item2"), "Bag should contain 'item2' after addition"); } @Test @@ -106,6 +107,53 @@ void testRemoveMethodThrowsException() { Bag bag = new Bag<>(); bag.add("item1"); Iterator iterator = bag.iterator(); - org.junit.jupiter.api.Assertions.assertThrows(UnsupportedOperationException.class, iterator::remove, "Remove operation should throw UnsupportedOperationException"); + assertThrows(UnsupportedOperationException.class, iterator::remove, "Remove operation should throw UnsupportedOperationException"); + } + + @Test + void testMultipleDuplicates() { + Bag bag = new Bag<>(); + bag.add("item1"); + bag.add("item1"); + bag.add("item1"); // Add three duplicates + + assertEquals(3, bag.size(), "Bag size should be 3 after adding three duplicates"); + assertTrue(bag.contains("item1"), "Bag should contain 'item1'"); + } + + @Test + void testLargeNumberOfElements() { + Bag bag = new Bag<>(); + for (int i = 0; i < 1000; i++) { + bag.add(i); + } + assertEquals(1000, bag.size(), "Bag should contain 1000 elements"); + } + + @Test + void testMixedTypeElements() { + Bag bag = new Bag<>(); + bag.add("string"); + bag.add(1); + bag.add(2.0); + + assertTrue(bag.contains("string"), "Bag should contain a string"); + assertTrue(bag.contains(1), "Bag should contain an integer"); + assertTrue(bag.contains(2.0), "Bag should contain a double"); + } + + @Test + void testIteratorWithDuplicates() { + Bag bag = new Bag<>(); + bag.add("item1"); + bag.add("item1"); + bag.add("item2"); + + int count = 0; + for (String item : bag) { + assertTrue(item.equals("item1") || item.equals("item2"), "Item should be either 'item1' or 'item2'"); + count++; + } + assertEquals(3, count, "Iterator should traverse all 3 items including duplicates"); } } diff --git a/src/test/java/com/thealgorithms/datastructures/bloomfilter/BloomFilterTest.java b/src/test/java/com/thealgorithms/datastructures/bloomfilter/BloomFilterTest.java index b19801a5ad71..048eb7e481a7 100644 --- a/src/test/java/com/thealgorithms/datastructures/bloomfilter/BloomFilterTest.java +++ b/src/test/java/com/thealgorithms/datastructures/bloomfilter/BloomFilterTest.java @@ -62,4 +62,55 @@ void testMultipleInsertions() { Assertions.assertFalse(bloomFilter.contains("key" + 200)); } + + @Test + void testEmptyFilterContains() { + Assertions.assertFalse(bloomFilter.contains("notInserted"), "Filter should not contain any elements when empty"); + Assertions.assertFalse(bloomFilter.contains(null), "Filter should not contain null elements"); + } + + @Test + void testDifferentTypes() { + BloomFilter filter = new BloomFilter<>(3, 100); + filter.insert("string"); + filter.insert(123); + filter.insert(45.67); + + Assertions.assertTrue(filter.contains("string"), "Filter should contain the string 'string'"); + Assertions.assertTrue(filter.contains(123), "Filter should contain the integer 123"); + Assertions.assertTrue(filter.contains(45.67), "Filter should contain the double 45.67"); + Assertions.assertFalse(filter.contains("missing"), "Filter should not contain elements that were not inserted"); + } + + @Test + void testFalsePositiveAfterInsertions() { + bloomFilter.insert("cat"); + bloomFilter.insert("dog"); + bloomFilter.insert("fish"); + + // Checking for an element that was not added + Assertions.assertFalse(bloomFilter.contains("bird"), "Filter should not contain 'bird' which was never inserted"); + + // To increase chances of false positives, we can add more items + for (int i = 0; i < 100; i++) { + bloomFilter.insert("item" + i); + } + + Assertions.assertFalse(bloomFilter.contains("nonexistent"), "Filter should not contain 'nonexistent' which was never inserted"); + } + + @Test + void testBoundaryConditions() { + BloomFilter filter = new BloomFilter<>(3, 10); + filter.insert("a"); + filter.insert("b"); + filter.insert("c"); + filter.insert("d"); + + Assertions.assertTrue(filter.contains("a"), "Filter should contain 'a'"); + Assertions.assertTrue(filter.contains("b"), "Filter should contain 'b'"); + Assertions.assertTrue(filter.contains("c"), "Filter should contain 'c'"); + Assertions.assertTrue(filter.contains("d"), "Filter should contain 'd'"); + Assertions.assertFalse(filter.contains("e"), "Filter should not contain 'e' which was not inserted"); + } } diff --git a/src/test/java/com/thealgorithms/datastructures/buffers/CircularBufferTest.java b/src/test/java/com/thealgorithms/datastructures/buffers/CircularBufferTest.java index be98fde484fa..b115fc187b1a 100644 --- a/src/test/java/com/thealgorithms/datastructures/buffers/CircularBufferTest.java +++ b/src/test/java/com/thealgorithms/datastructures/buffers/CircularBufferTest.java @@ -1,143 +1,88 @@ package com.thealgorithms.datastructures.buffers; 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.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicIntegerArray; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; class CircularBufferTest { - private static final int BUFFER_SIZE = 10; - private CircularBuffer buffer; - - @BeforeEach - void setUp() { - buffer = new CircularBuffer<>(BUFFER_SIZE); - } @Test - void isEmpty() { + void testInitialization() { + CircularBuffer buffer = new CircularBuffer<>(5); assertTrue(buffer.isEmpty()); - buffer.put(generateInt()); - assertFalse(buffer.isEmpty()); + assertEquals(Boolean.FALSE, buffer.isFull()); } @Test - void isFull() { - assertFalse(buffer.isFull()); - buffer.put(generateInt()); - assertFalse(buffer.isFull()); + void testPutAndGet() { + CircularBuffer buffer = new CircularBuffer<>(3); - for (int i = 1; i < BUFFER_SIZE; i++) { - buffer.put(generateInt()); - } + assertTrue(buffer.put("A")); + assertEquals(Boolean.FALSE, buffer.isEmpty()); + assertEquals(Boolean.FALSE, buffer.isFull()); + + buffer.put("B"); + buffer.put("C"); assertTrue(buffer.isFull()); + + assertEquals("A", buffer.get()); + assertEquals("B", buffer.get()); + assertEquals("C", buffer.get()); + assertTrue(buffer.isEmpty()); } @Test - void get() { - assertNull(buffer.get()); - for (int i = 0; i < 100; i++) { - buffer.put(i); - } - for (int i = 0; i < BUFFER_SIZE; i++) { - assertEquals(i, buffer.get()); - } + void testOverwrite() { + CircularBuffer buffer = new CircularBuffer<>(3); + + buffer.put(1); + buffer.put(2); + buffer.put(3); + assertEquals(Boolean.FALSE, buffer.put(4)); // This should overwrite 1 + + assertEquals(2, buffer.get()); + assertEquals(3, buffer.get()); + assertEquals(4, buffer.get()); assertNull(buffer.get()); } @Test - void put() { - for (int i = 0; i < BUFFER_SIZE; i++) { - assertTrue(buffer.put(generateInt())); - } - assertFalse(buffer.put(generateInt())); + void testEmptyBuffer() { + CircularBuffer buffer = new CircularBuffer<>(2); + assertNull(buffer.get()); } - @RepeatedTest(1000) - void concurrentTest() throws InterruptedException { - final int numberOfThreadsForProducers = 3; - final int numberOfThreadsForConsumers = 2; - final int numberOfItems = 300; - final CountDownLatch producerCountDownLatch = new CountDownLatch(numberOfItems); - final CountDownLatch consumerCountDownLatch = new CountDownLatch(numberOfItems); - final AtomicIntegerArray resultAtomicArray = new AtomicIntegerArray(numberOfItems); - - // We are running 2 ExecutorService simultaneously 1 - producer, 2 - consumer - // Run producer threads to populate buffer. - ExecutorService putExecutors = Executors.newFixedThreadPool(numberOfThreadsForProducers); - putExecutors.execute(() -> { - while (producerCountDownLatch.getCount() > 0) { - int count = (int) producerCountDownLatch.getCount(); - boolean put = buffer.put(count); - while (!put) { - put = buffer.put(count); - } - producerCountDownLatch.countDown(); - } - }); - - // Run consumer threads to retrieve the data from buffer. - ExecutorService getExecutors = Executors.newFixedThreadPool(numberOfThreadsForConsumers); - getExecutors.execute(() -> { - while (consumerCountDownLatch.getCount() > 0) { - int count = (int) consumerCountDownLatch.getCount(); - Integer item = buffer.get(); - while (item == null) { - item = buffer.get(); - } - resultAtomicArray.set(count - 1, item); - consumerCountDownLatch.countDown(); - } - }); - - producerCountDownLatch.await(); - consumerCountDownLatch.await(); - putExecutors.shutdown(); - getExecutors.shutdown(); - shutDownExecutorSafely(putExecutors); - shutDownExecutorSafely(getExecutors); - - List resultArray = getSortedListFrom(resultAtomicArray); - for (int i = 0; i < numberOfItems; i++) { - int expectedItem = i + 1; - assertEquals(expectedItem, resultArray.get(i)); - } + @Test + void testFullBuffer() { + CircularBuffer buffer = new CircularBuffer<>(2); + buffer.put('A'); + buffer.put('B'); + assertTrue(buffer.isFull()); + assertEquals(Boolean.FALSE, buffer.put('C')); // This should overwrite 'A' + assertEquals('B', buffer.get()); + assertEquals('C', buffer.get()); } - private int generateInt() { - return ThreadLocalRandom.current().nextInt(0, 100); - } + @Test + void testIllegalArguments() { + assertThrows(IllegalArgumentException.class, () -> new CircularBuffer<>(0)); + assertThrows(IllegalArgumentException.class, () -> new CircularBuffer<>(-1)); - private void shutDownExecutorSafely(ExecutorService executorService) { - try { - if (!executorService.awaitTermination(1_000, TimeUnit.MILLISECONDS)) { - executorService.shutdownNow(); - } - } catch (InterruptedException e) { - executorService.shutdownNow(); - } + CircularBuffer buffer = new CircularBuffer<>(1); + assertThrows(IllegalArgumentException.class, () -> buffer.put(null)); } - public List getSortedListFrom(AtomicIntegerArray atomicArray) { - int length = atomicArray.length(); - ArrayList result = new ArrayList<>(length); - for (int i = 0; i < length; i++) { - result.add(atomicArray.get(i)); + @Test + void testLargeBuffer() { + CircularBuffer buffer = new CircularBuffer<>(1000); + for (int i = 0; i < 1000; i++) { + buffer.put(i); } - result.sort(Comparator.comparingInt(o -> o)); - return result; + assertTrue(buffer.isFull()); + buffer.put(1000); // This should overwrite 0 + assertEquals(1, buffer.get()); } } diff --git a/src/test/java/com/thealgorithms/datastructures/caches/LFUCacheTest.java b/src/test/java/com/thealgorithms/datastructures/caches/LFUCacheTest.java index 6a94345d625e..8ad927564aa5 100644 --- a/src/test/java/com/thealgorithms/datastructures/caches/LFUCacheTest.java +++ b/src/test/java/com/thealgorithms/datastructures/caches/LFUCacheTest.java @@ -1,6 +1,8 @@ package com.thealgorithms.datastructures.caches; 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; @@ -22,7 +24,7 @@ void testLFUCacheWithIntegerValueShouldPass() { lfuCache.put(6, 60); // will return null as value with key 2 is now evicted - assertEquals(null, lfuCache.get(2)); + assertNull(lfuCache.get(2)); // should return 60 assertEquals(60, lfuCache.get(6)); @@ -30,7 +32,7 @@ void testLFUCacheWithIntegerValueShouldPass() { // this operation will remove value with key as 3 lfuCache.put(7, 70); - assertEquals(null, lfuCache.get(2)); + assertNull(lfuCache.get(2)); assertEquals(70, lfuCache.get(7)); } @@ -41,7 +43,7 @@ void testLFUCacheWithStringValueShouldPass() { lfuCache.put(2, "Beta"); lfuCache.put(3, "Gamma"); lfuCache.put(4, "Delta"); - lfuCache.put(5, "Eplison"); + lfuCache.put(5, "Epsilon"); // get method call will increase frequency of key 1 by 1 assertEquals("Alpha", lfuCache.get(1)); @@ -50,7 +52,7 @@ void testLFUCacheWithStringValueShouldPass() { lfuCache.put(6, "Digamma"); // will return null as value with key 2 is now evicted - assertEquals(null, lfuCache.get(2)); + assertNull(lfuCache.get(2)); // should return string Digamma assertEquals("Digamma", lfuCache.get(6)); @@ -58,25 +60,79 @@ void testLFUCacheWithStringValueShouldPass() { // this operation will remove value with key as 3 lfuCache.put(7, "Zeta"); - assertEquals(null, lfuCache.get(2)); + assertNull(lfuCache.get(2)); assertEquals("Zeta", lfuCache.get(7)); } - /** - * test addNodeWithUpdatedFrequency method - * @author yuluo - */ @Test - void testAddNodeWithUpdatedFrequency() { + void testUpdateValueShouldPreserveFrequency() { LFUCache lfuCache = new LFUCache<>(3); - lfuCache.put(1, "beijing"); - lfuCache.put(2, "shanghai"); - lfuCache.put(3, "gansu"); + lfuCache.put(1, "A"); + lfuCache.put(2, "B"); + lfuCache.put(3, "C"); - assertEquals("beijing", lfuCache.get(1)); + assertEquals("A", lfuCache.get(1)); // Accessing key 1 + lfuCache.put(4, "D"); // This should evict key 2 - lfuCache.put(1, "shanxi"); + assertNull(lfuCache.get(2)); // Key 2 should be evicted + assertEquals("C", lfuCache.get(3)); // Key 3 should still exist + assertEquals("A", lfuCache.get(1)); // Key 1 should still exist - assertEquals("shanxi", lfuCache.get(1)); + lfuCache.put(1, "Updated A"); // Update the value of key 1 + assertEquals("Updated A", lfuCache.get(1)); // Check if the update was successful + } + + @Test + void testEvictionPolicyWhenFull() { + LFUCache lfuCache = new LFUCache<>(2); + lfuCache.put(1, "One"); + lfuCache.put(2, "Two"); + + assertEquals("One", lfuCache.get(1)); // Access key 1 + lfuCache.put(3, "Three"); // This should evict key 2 (least frequently used) + + assertNull(lfuCache.get(2)); // Key 2 should be evicted + assertEquals("One", lfuCache.get(1)); // Key 1 should still exist + assertEquals("Three", lfuCache.get(3)); // Check if key 3 exists + } + + @Test + void testGetFromEmptyCacheShouldReturnNull() { + LFUCache lfuCache = new LFUCache<>(3); + assertNull(lfuCache.get(1)); // Should return null as the cache is empty + } + + @Test + void testPutNullValueShouldStoreNull() { + LFUCache lfuCache = new LFUCache<>(3); + lfuCache.put(1, null); // Store a null value + + assertNull(lfuCache.get(1)); // Should return null + } + + @Test + void testInvalidCacheCapacityShouldThrowException() { + assertThrows(IllegalArgumentException.class, () -> new LFUCache<>(0)); + assertThrows(IllegalArgumentException.class, () -> new LFUCache<>(-1)); + } + + @Test + void testMultipleAccessPatterns() { + LFUCache lfuCache = new LFUCache<>(5); + lfuCache.put(1, "A"); + lfuCache.put(2, "B"); + lfuCache.put(3, "C"); + lfuCache.put(4, "D"); + + assertEquals("A", lfuCache.get(1)); // Access 1 + lfuCache.put(5, "E"); // Should not evict anything yet + lfuCache.put(6, "F"); // Evict B + + assertNull(lfuCache.get(2)); // B should be evicted + assertEquals("C", lfuCache.get(3)); // C should still exist + assertEquals("D", lfuCache.get(4)); // D should still exist + assertEquals("A", lfuCache.get(1)); // A should still exist + assertEquals("E", lfuCache.get(5)); // E should exist + assertEquals("F", lfuCache.get(6)); // F should exist } } diff --git a/src/test/java/com/thealgorithms/datastructures/caches/LRUCacheTest.java b/src/test/java/com/thealgorithms/datastructures/caches/LRUCacheTest.java index c56ada060022..99b9952435c4 100644 --- a/src/test/java/com/thealgorithms/datastructures/caches/LRUCacheTest.java +++ b/src/test/java/com/thealgorithms/datastructures/caches/LRUCacheTest.java @@ -3,56 +3,147 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; public class LRUCacheTest { - private static final int SIZE = 5; + private LRUCache cache; + + @BeforeEach + void setUp() { + cache = new LRUCache<>(SIZE); + } @Test - public void putAndGetIntegerValues() { - LRUCache lruCache = new LRUCache<>(SIZE); + public void testBasicOperations() { + cache.put(1, 100); + assertEquals(100, cache.get(1)); + assertNull(cache.get(2)); + } + @Test + public void testEvictionPolicy() { + // Fill cache to capacity for (int i = 0; i < SIZE; i++) { - lruCache.put(i, i); + cache.put(i, i * 100); } + // Verify all elements are present for (int i = 0; i < SIZE; i++) { - assertEquals(i, lruCache.get(i)); + assertEquals(i * 100, cache.get(i)); } + + // Add one more element, causing eviction of least recently used + cache.put(SIZE, SIZE * 100); + + // First element should be evicted + assertNull(cache.get(0)); + assertEquals(SIZE * 100, cache.get(SIZE)); } @Test - public void putAndGetStringValues() { - LRUCache lruCache = new LRUCache<>(SIZE); - + public void testAccessOrder() { + // Fill cache for (int i = 0; i < SIZE; i++) { - lruCache.put("key" + i, "value" + i); + cache.put(i, i); } - for (int i = 0; i < SIZE; i++) { - assertEquals("value" + i, lruCache.get("key" + i)); - } + // Access first element, making it most recently used + cache.get(0); + + // Add new element, should evict second element (1) + cache.put(SIZE, SIZE); + + assertEquals(0, cache.get(0)); // Should still exist + assertNull(cache.get(1)); // Should be evicted + assertEquals(SIZE, cache.get(SIZE)); // Should exist + } + + @Test + public void testUpdateExistingKey() { + cache.put(1, 100); + assertEquals(100, cache.get(1)); + + // Update existing key + cache.put(1, 200); + assertEquals(200, cache.get(1)); } @Test - public void nullKeysAndValues() { - LRUCache mruCache = new LRUCache<>(SIZE); - mruCache.put(null, 2); - mruCache.put(6, null); + public void testNullValues() { + cache.put(1, null); + assertNull(cache.get(1)); - assertEquals(2, mruCache.get(null)); - assertNull(mruCache.get(6)); + // Update null to non-null + cache.put(1, 100); + assertEquals(100, cache.get(1)); + + // Update non-null to null + cache.put(1, null); + assertNull(cache.get(1)); + } + + @Test + public void testStringKeysAndValues() { + LRUCache stringCache = new LRUCache<>(SIZE); + + stringCache.put("key1", "value1"); + stringCache.put("key2", "value2"); + + assertEquals("value1", stringCache.get("key1")); + assertEquals("value2", stringCache.get("key2")); + } + + @Test + public void testLongSequenceOfOperations() { + // Add elements beyond capacity multiple times + for (int i = 0; i < SIZE * 3; i++) { + cache.put(i, i * 100); + + // Verify only the last SIZE elements are present + for (int j = Math.max(0, i - SIZE + 1); j <= i; j++) { + assertEquals(j * 100, cache.get(j)); + } + + // Verify elements before the window are evicted + if (i >= SIZE) { + assertNull(cache.get(i - SIZE)); + } + } } @Test - public void overCapacity() { - LRUCache mruCache = new LRUCache<>(SIZE); + void testCustomObjects() { + class TestObject { + private final String value; - for (int i = 0; i < 10; i++) { - mruCache.put(i, i); + TestObject(String value) { + this.value = value; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof TestObject) { + return value.equals(((TestObject) obj).value); + } + return false; + } + + @Override + public int hashCode() { + return value == null ? 0 : value.hashCode(); + } } - assertEquals(9, mruCache.get(9)); + LRUCache objectCache = new LRUCache<>(SIZE); + TestObject obj1 = new TestObject("test1"); + TestObject obj2 = new TestObject("test2"); + + objectCache.put(1, obj1); + objectCache.put(2, obj2); + + assertEquals(obj1, objectCache.get(1)); + assertEquals(obj2, objectCache.get(2)); } } diff --git a/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java b/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java index 447feb38e788..50303ba239f6 100644 --- a/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java +++ b/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java @@ -11,27 +11,27 @@ public class MRUCacheTest { @Test public void putAndGetIntegerValues() { - MRUCache lruCache = new MRUCache<>(SIZE); + MRUCache mruCache = new MRUCache<>(SIZE); for (int i = 0; i < SIZE; i++) { - lruCache.put(i, i); + mruCache.put(i, i); } for (int i = 0; i < SIZE; i++) { - assertEquals(i, lruCache.get(i)); + assertEquals(i, mruCache.get(i)); } } @Test public void putAndGetStringValues() { - MRUCache lruCache = new MRUCache<>(SIZE); + MRUCache mruCache = new MRUCache<>(SIZE); for (int i = 0; i < SIZE; i++) { - lruCache.put("key" + i, "value" + i); + mruCache.put("key" + i, "value" + i); } for (int i = 0; i < SIZE; i++) { - assertEquals("value" + i, lruCache.get("key" + i)); + assertEquals("value" + i, mruCache.get("key" + i)); } } @@ -53,6 +53,73 @@ public void overCapacity() { mruCache.put(i, i); } - assertEquals(9, mruCache.get(9)); + // After inserting 10 items, the cache should have evicted the least recently used ones. + assertEquals(9, mruCache.get(9)); // Most recently used + assertEquals(0, mruCache.get(0)); // Least recently used, should be evicted + } + + @Test + public void overwriteExistingKey() { + MRUCache mruCache = new MRUCache<>(SIZE); + mruCache.put(1, "one"); + mruCache.put(1, "uno"); // Overwriting the value for key 1 + + assertEquals("uno", mruCache.get(1)); + assertNull(mruCache.get(2)); // Ensure other keys are unaffected + } + + @Test + public void evictionOrder() { + MRUCache mruCache = new MRUCache<>(SIZE); + + for (int i = 0; i < SIZE; i++) { + mruCache.put(i, i); + } + + // Access a key to make it most recently used + mruCache.get(2); + + // Add new items to trigger eviction + mruCache.put(5, 5); + mruCache.put(6, 6); + + // Key 3 should be evicted since 2 is the most recently used + assertEquals(3, mruCache.get(3)); + assertEquals(4, mruCache.get(4)); // Key 4 should still be available + assertEquals(6, mruCache.get(6)); // Key 6 should be available + } + + @Test + public void cacheHandlesLargeValues() { + MRUCache mruCache = new MRUCache<>(SIZE); + + for (int i = 0; i < SIZE; i++) { + mruCache.put("key" + i, "value" + i); + } + + // Verify values + for (int i = 0; i < SIZE; i++) { + assertEquals("value" + i, mruCache.get("key" + i)); + } + + // Add large value + mruCache.put("largeKey", "largeValue"); + + // Verify eviction of the least recently used (key 0 should be evicted) + assertEquals("value0", mruCache.get("key0")); + assertEquals("largeValue", mruCache.get("largeKey")); + } + + @Test + public void testEmptyCacheBehavior() { + MRUCache mruCache = new MRUCache<>(SIZE); + + // Verify that accessing any key returns null + assertNull(mruCache.get(1)); + assertNull(mruCache.get(100)); + + // Adding to cache and checking again + mruCache.put(1, 10); + assertEquals(10, mruCache.get(1)); } } diff --git a/src/test/java/com/thealgorithms/datastructures/crdt/LWWElementSetTest.java b/src/test/java/com/thealgorithms/datastructures/crdt/LWWElementSetTest.java index 36593d6669f8..0356949a8f69 100644 --- a/src/test/java/com/thealgorithms/datastructures/crdt/LWWElementSetTest.java +++ b/src/test/java/com/thealgorithms/datastructures/crdt/LWWElementSetTest.java @@ -3,106 +3,96 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.BeforeEach; +import java.time.Instant; import org.junit.jupiter.api.Test; class LWWElementSetTest { - private LWWElementSet set; - private final Bias bias = Bias.ADDS; - - @BeforeEach - void setUp() { - set = new LWWElementSet(); - } - @Test - void testAdd() { - Element element = new Element("key1", 1, bias); - set.add(element); - - assertTrue(set.lookup(element)); + void testAddElement() { + LWWElementSet set = new LWWElementSet<>(); + set.add("A"); + assertTrue(set.lookup("A")); } @Test - void testRemove() { - Element element = new Element("key1", 1, bias); - set.add(element); - set.remove(element); - - assertFalse(set.lookup(element)); + void testRemoveElement() { + LWWElementSet set = new LWWElementSet<>(); + set.add("A"); + set.remove("A"); + assertFalse(set.lookup("A")); } @Test - void testRemoveNonexistentElement() { - Element element = new Element("key1", 1, bias); - set.remove(element); - - assertFalse(set.lookup(element)); + void testLookupWithoutAdding() { + LWWElementSet set = new LWWElementSet<>(); + assertFalse(set.lookup("A")); } @Test - void testLookupNonexistentElement() { - Element element = new Element("key1", 1, bias); + void testLookupLaterTimestampsFalse() { + LWWElementSet set = new LWWElementSet<>(); + + set.addSet.put("A", new Element<>("A", Instant.now())); + set.removeSet.put("A", new Element<>("A", Instant.now().plusSeconds(10))); - assertFalse(set.lookup(element)); + assertFalse(set.lookup("A")); } @Test - void testCompareEqualSets() { - LWWElementSet otherSet = new LWWElementSet(); + void testLookupEarlierTimestampsTrue() { + LWWElementSet set = new LWWElementSet<>(); - Element element = new Element("key1", 1, bias); - set.add(element); - otherSet.add(element); + set.addSet.put("A", new Element<>("A", Instant.now())); + set.removeSet.put("A", new Element<>("A", Instant.now().minusSeconds(10))); - assertTrue(set.compare(otherSet)); - - otherSet.add(new Element("key2", 2, bias)); - assertTrue(set.compare(otherSet)); + assertTrue(set.lookup("A")); } @Test - void testCompareDifferentSets() { - LWWElementSet otherSet = new LWWElementSet(); - - Element element1 = new Element("key1", 1, bias); - Element element2 = new Element("key2", 2, bias); - - set.add(element1); - otherSet.add(element2); - - assertFalse(set.compare(otherSet)); + void testLookupWithConcurrentTimestamps() { + LWWElementSet set = new LWWElementSet<>(); + Instant now = Instant.now(); + set.addSet.put("A", new Element<>("A", now)); + set.removeSet.put("A", new Element<>("A", now)); + assertFalse(set.lookup("A")); } @Test - void testMerge() { - LWWElementSet otherSet = new LWWElementSet(); + void testMergeTwoSets() { + LWWElementSet set1 = new LWWElementSet<>(); + LWWElementSet set2 = new LWWElementSet<>(); - Element element1 = new Element("key1", 1, bias); - Element element2 = new Element("key2", 2, bias); + set1.add("A"); + set2.add("B"); + set2.remove("A"); - set.add(element1); - otherSet.add(element2); + set1.merge(set2); - set.merge(otherSet); - - assertTrue(set.lookup(element1)); - assertTrue(set.lookup(element2)); + assertFalse(set1.lookup("A")); + assertTrue(set1.lookup("B")); } @Test - void testCompareTimestampsEqualTimestamps() { - LWWElementSet lwwElementSet = new LWWElementSet(); + void testMergeWithConflictingTimestamps() { + LWWElementSet set1 = new LWWElementSet<>(); + LWWElementSet set2 = new LWWElementSet<>(); - Element e1 = new Element("key1", 10, Bias.REMOVALS); - Element e2 = new Element("key1", 10, Bias.REMOVALS); + Instant now = Instant.now(); + set1.addSet.put("A", new Element<>("A", now.minusSeconds(10))); + set2.addSet.put("A", new Element<>("A", now)); - assertTrue(lwwElementSet.compareTimestamps(e1, e2)); + set1.merge(set2); - e1 = new Element("key1", 10, Bias.ADDS); - e2 = new Element("key1", 10, Bias.ADDS); + assertTrue(set1.lookup("A")); + } - assertFalse(lwwElementSet.compareTimestamps(e1, e2)); + @Test + void testRemoveOlderThanAdd() { + LWWElementSet set = new LWWElementSet<>(); + Instant now = Instant.now(); + set.addSet.put("A", new Element<>("A", now)); + set.removeSet.put("A", new Element<>("A", now.minusSeconds(10))); + assertTrue(set.lookup("A")); } } diff --git a/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java b/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java index 8e067086689b..8fdc93e1ca22 100644 --- a/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java +++ b/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java @@ -24,6 +24,8 @@ public void setUp() { public void testGetElement() { array.add("Alice"); array.add("Bob"); + array.add("Charlie"); + array.add("David"); assertEquals("Bob", array.get(1)); } @@ -31,6 +33,7 @@ public void testGetElement() { public void testGetInvalidIndex() { assertThrows(IndexOutOfBoundsException.class, () -> array.get(-1)); assertThrows(IndexOutOfBoundsException.class, () -> array.get(10)); + assertThrows(IndexOutOfBoundsException.class, () -> array.get(100)); } @Test diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java new file mode 100644 index 000000000000..810773555a63 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java @@ -0,0 +1,47 @@ +package com.thealgorithms.datastructures.graphs; + +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.ArrayList; +import java.util.Arrays; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class AStarTest { + + private AStar.Graph graph; + private int[] heuristic; + + @BeforeEach + public void setUp() { + // Initialize graph and heuristic values for testing + graph = new AStar.Graph(5); + ArrayList graphData = new ArrayList<>(Arrays.asList(0, 1, 1, null, 0, 2, 2, null, 1, 3, 1, null, 2, 3, 1, null, 3, 4, 1, null)); + AStar.initializeGraph(graph, graphData); + + heuristic = new int[] {5, 4, 3, 2, 0}; // Heuristic values for each node + } + + @Test + public void testAStarFindsPath() { + AStar.PathAndDistance result = AStar.aStar(0, 4, graph, heuristic); + assertEquals(3, result.getDistance(), "Expected distance from 0 to 4 is 3"); + assertEquals(Arrays.asList(0, 1, 3, 4), result.getPath(), "Expected path from 0 to 4"); + } + + @Test + public void testAStarPathNotFound() { + AStar.PathAndDistance result = AStar.aStar(0, 5, graph, heuristic); // Node 5 does not exist + assertEquals(-1, result.getDistance(), "Expected distance when path not found is -1"); + assertNull(result.getPath(), "Expected path should be null when no path exists"); + } + + @Test + public void testAStarSameNode() { + AStar.PathAndDistance result = AStar.aStar(0, 0, graph, heuristic); + assertEquals(0, result.getDistance(), "Expected distance from 0 to 0 is 0"); + assertEquals(singletonList(0), result.getPath(), "Expected path should only contain the start node"); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/BoruvkaAlgorithmTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/BoruvkaAlgorithmTest.java index 8cd0b0a6838f..f089169903d6 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/BoruvkaAlgorithmTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/BoruvkaAlgorithmTest.java @@ -183,7 +183,7 @@ void testEdgesRange() { * @param result list of edges in the Minimum Spanning Tree * @return the total weight of the Minimum Spanning Tree */ - int computeTotalWeight(final List result) { + int computeTotalWeight(final Iterable result) { int totalWeight = 0; for (final var edge : result) { totalWeight += edge.weight; diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithmTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithmTest.java new file mode 100644 index 000000000000..bf4e2828e069 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithmTest.java @@ -0,0 +1,64 @@ +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/EdmondsBlossomAlgorithmTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithmTest.java index 4a7232447e50..aa8e6beeb3db 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithmTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithmTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; import org.junit.jupiter.api.Test; @@ -25,7 +26,7 @@ public class EdmondsBlossomAlgorithmTest { * @param matching List of matched pairs returned by the algorithm. * @return A sorted 2D array of matching pairs. */ - private int[][] convertMatchingToArray(List matching) { + private int[][] convertMatchingToArray(Collection matching) { // Convert the list of pairs into an array int[][] result = matching.toArray(new int[0][]); diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/FloydWarshallTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/FloydWarshallTest.java new file mode 100644 index 000000000000..7d6a2b239f4b --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/graphs/FloydWarshallTest.java @@ -0,0 +1,33 @@ +package com.thealgorithms.datastructures.graphs; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.Test; + +class FloydWarshallTest { + + @Test + void testSmallGraph() { + int[][] adjacencyMatrix = {{0, 0, 0, 0}, // Ignored row (0 index) + {0, 0, 3, FloydWarshall.INFINITY}, {0, FloydWarshall.INFINITY, 0, 1}, {0, FloydWarshall.INFINITY, FloydWarshall.INFINITY, 0}}; + + FloydWarshall fw = new FloydWarshall(3); + fw.floydwarshall(adjacencyMatrix); + + int[][] expectedDistanceMatrix = {{0, 0, 0, 0}, {0, 0, 3, 4}, {0, FloydWarshall.INFINITY, 0, 1}, {0, FloydWarshall.INFINITY, FloydWarshall.INFINITY, 0}}; + + assertArrayEquals(expectedDistanceMatrix, fw.getDistanceMatrix()); + } + + @Test + void testLargerGraph() { + int[][] adjacencyMatrix = {{0, 0, 0, 0, 0}, {0, 0, 1, FloydWarshall.INFINITY, 2}, {0, FloydWarshall.INFINITY, 0, 4, FloydWarshall.INFINITY}, {0, FloydWarshall.INFINITY, FloydWarshall.INFINITY, 0, 3}, {0, FloydWarshall.INFINITY, FloydWarshall.INFINITY, FloydWarshall.INFINITY, 0}}; + + FloydWarshall fw = new FloydWarshall(4); + fw.floydwarshall(adjacencyMatrix); + + int[][] expectedDistanceMatrix = {{0, 0, 0, 0, 0}, {0, 0, 1, 5, 2}, {0, FloydWarshall.INFINITY, 0, 4, 7}, {0, FloydWarshall.INFINITY, FloydWarshall.INFINITY, 0, 3}, {0, FloydWarshall.INFINITY, FloydWarshall.INFINITY, FloydWarshall.INFINITY, 0}}; + + assertArrayEquals(expectedDistanceMatrix, fw.getDistanceMatrix()); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/FordFulkersonTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/FordFulkersonTest.java index 908296aab5c1..29e373e361ad 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/FordFulkersonTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/FordFulkersonTest.java @@ -91,4 +91,126 @@ public void testComplexNetwork() { int maxFlow = FordFulkerson.networkFlow(vertexCount, capacity, flow, 0, 4); assertEquals(19, maxFlow); } + + @Test + public void testLargeNetwork() { + int vertexCount = 8; + int[][] capacity = new int[vertexCount][vertexCount]; + int[][] flow = new int[vertexCount][vertexCount]; + + // Setting up a large network + capacity[0][1] = 10; + capacity[0][2] = 5; + capacity[1][3] = 15; + capacity[2][3] = 10; + capacity[1][4] = 10; + capacity[3][5] = 10; + capacity[4][5] = 5; + capacity[4][6] = 10; + capacity[5][7] = 10; + capacity[6][7] = 15; + + int maxFlow = FordFulkerson.networkFlow(vertexCount, capacity, flow, 0, 7); + assertEquals(15, maxFlow); // Maximum flow should be 15 + } + + @Test + public void testMultipleSourcesAndSinks() { + int vertexCount = 7; + int[][] capacity = new int[vertexCount][vertexCount]; + int[][] flow = new int[vertexCount][vertexCount]; + + // Creating multiple sources and sinks scenario + capacity[0][1] = 10; // Source 1 + capacity[0][2] = 5; + capacity[1][3] = 15; + capacity[2][3] = 10; + capacity[3][4] = 10; // Sink 1 + capacity[3][5] = 5; + capacity[3][6] = 10; // Sink 2 + capacity[5][6] = 10; + + int maxFlow = FordFulkerson.networkFlow(vertexCount, capacity, flow, 0, 4); + assertEquals(10, maxFlow); // Maximum flow should be 10 + } + + @Test + public void testDisconnectedGraph() { + int vertexCount = 6; + int[][] capacity = new int[vertexCount][vertexCount]; + int[][] flow = new int[vertexCount][vertexCount]; + + // No connection between source and sink + capacity[0][1] = 10; // Only one edge not connected to the sink + capacity[1][2] = 10; + capacity[3][4] = 10; + + int maxFlow = FordFulkerson.networkFlow(vertexCount, capacity, flow, 0, 5); + assertEquals(0, maxFlow); // No flow should be possible + } + + @Test + public void testZeroCapacityEdge() { + int vertexCount = 4; + int[][] capacity = new int[vertexCount][vertexCount]; + int[][] flow = new int[vertexCount][vertexCount]; + + // Including a zero capacity edge + capacity[0][1] = 10; + capacity[0][2] = 0; // Zero capacity + capacity[1][3] = 5; + capacity[2][3] = 10; + + int maxFlow = FordFulkerson.networkFlow(vertexCount, capacity, flow, 0, 3); + assertEquals(5, maxFlow); // Flow only possible through 0 -> 1 -> 3 + } + + @Test + public void testAllEdgesZeroCapacity() { + int vertexCount = 5; + int[][] capacity = new int[vertexCount][vertexCount]; + int[][] flow = new int[vertexCount][vertexCount]; + + // All edges with zero capacity + capacity[0][1] = 0; + capacity[1][2] = 0; + capacity[2][3] = 0; + capacity[3][4] = 0; + + int maxFlow = FordFulkerson.networkFlow(vertexCount, capacity, flow, 0, 4); + assertEquals(0, maxFlow); // No flow should be possible + } + + @Test + public void testCycleGraph() { + int vertexCount = 4; + int[][] capacity = new int[vertexCount][vertexCount]; + int[][] flow = new int[vertexCount][vertexCount]; + + // Setting up a cycle + capacity[0][1] = 10; + capacity[1][2] = 5; + capacity[2][0] = 5; // This creates a cycle + capacity[1][3] = 15; + capacity[2][3] = 10; + + int maxFlow = FordFulkerson.networkFlow(vertexCount, capacity, flow, 0, 3); + assertEquals(10, maxFlow); // Maximum flow should be 10 + } + + @Test + public void testFlowWithExcessCapacity() { + int vertexCount = 5; + int[][] capacity = new int[vertexCount][vertexCount]; + int[][] flow = new int[vertexCount][vertexCount]; + + // Extra capacity in the flow + capacity[0][1] = 20; + capacity[1][2] = 10; + capacity[2][3] = 15; + capacity[1][3] = 5; + + int maxFlow = FordFulkerson.networkFlow(vertexCount, capacity, flow, 0, 3); + assertEquals(15, maxFlow); // Maximum flow should be 15 (20 from 0->1 and 10->2, limited by 15->3) + } } diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/HamiltonianCycleTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/HamiltonianCycleTest.java index ed7c886d784e..4529606d7797 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/HamiltonianCycleTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/HamiltonianCycleTest.java @@ -6,7 +6,7 @@ class HamiltonianCycleTest { - private HamiltonianCycle hamiltonianCycle = new HamiltonianCycle(); + private final HamiltonianCycle hamiltonianCycle = new HamiltonianCycle(); @Test void testFindHamiltonianCycleShouldReturnHamiltonianCycle() { @@ -36,4 +36,63 @@ void testFindHamiltonianCycleShouldReturnInfinityArray() { assertArrayEquals(expectedArray, hamiltonianCycle.findHamiltonianCycle(inputArray)); } + + @Test + void testSingleVertexGraph() { + int[] expectedArray = {0, 0}; + int[][] inputArray = {{0}}; + + assertArrayEquals(expectedArray, hamiltonianCycle.findHamiltonianCycle(inputArray)); + } + + @Test + void testDisconnectedGraphShouldReturnInfinityArray() { + int[] expectedArray = {-1, -1, -1, -1, -1}; + int[][] inputArray = {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}; + + assertArrayEquals(expectedArray, hamiltonianCycle.findHamiltonianCycle(inputArray)); + } + + @Test + void testCompleteGraphShouldReturnHamiltonianCycle() { + int[] expectedArray = {0, 1, 2, 3, 4, 0}; + int[][] inputArray = { + {0, 1, 1, 1, 1}, + {1, 0, 1, 1, 1}, + {1, 1, 0, 1, 1}, + {1, 1, 1, 0, 1}, + {1, 1, 1, 1, 0}, + }; + + assertArrayEquals(expectedArray, hamiltonianCycle.findHamiltonianCycle(inputArray)); + } + + @Test + void testGraphWithNoEdgesShouldReturnInfinityArray() { + int[] expectedArray = {-1, -1, -1, -1, -1, -1}; + + int[][] inputArray = { + {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}, + }; + + assertArrayEquals(expectedArray, hamiltonianCycle.findHamiltonianCycle(inputArray)); + } + + @Test + void testLargeGraphWithHamiltonianCycle() { + int[] expectedArray = {0, 1, 2, 3, 4, 0}; + int[][] inputArray = { + {0, 1, 0, 1, 1}, + {1, 0, 1, 1, 0}, + {0, 1, 0, 1, 1}, + {1, 1, 1, 0, 1}, + {1, 0, 1, 1, 0}, + }; + + assertArrayEquals(expectedArray, hamiltonianCycle.findHamiltonianCycle(inputArray)); + } } diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithmTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithmTest.java new file mode 100644 index 000000000000..7ea2449202e0 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithmTest.java @@ -0,0 +1,142 @@ +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.Test; + +/** + * Unit tests for {@link JohnsonsAlgorithm} class. This class + * contains test cases to verify the correct implementation of + * various methods used in Johnson's Algorithm such as shortest path + * calculations, graph reweighting, and more. + */ +class JohnsonsAlgorithmTest { + + // Constant representing infinity + private static final double INF = Double.POSITIVE_INFINITY; + + /** + * Tests the Johnson's Algorithm with a simple graph without negative edges. + * Verifies that the algorithm returns the correct shortest path distances. + */ + @Test + void testSimpleGraph() { + double[][] graph = {{0, 4, INF, INF}, {INF, 0, 1, INF}, {INF, INF, 0, 2}, {INF, INF, INF, 0}}; + double[][] result = JohnsonsAlgorithm.johnsonAlgorithm(graph); + double[][] expected = {{0, 4, 5, 7}, {INF, 0, 1, 3}, {INF, INF, 0, 2}, {INF, INF, INF, 0}}; + assertArrayEquals(expected, result); + } + + /** + * Tests Johnson's Algorithm on a graph with negative edges but no negative weight cycles. + */ + @Test + void testGraphWithNegativeEdges() { + double[][] graph = {{0, -1, 4}, {INF, 0, 3}, {INF, INF, 0}}; + double[][] result = JohnsonsAlgorithm.johnsonAlgorithm(graph); + double[][] expected = {{0, INF, 4}, {INF, 0, 3}, {INF, INF, 0}}; + assertArrayEquals(expected, result); + } + + /** + * Tests Johnson's Algorithm on a graph with a negative weight cycle. + */ + @Test + void testNegativeWeightCycle() { + double[][] graph = {{0, 1, INF}, {INF, 0, -1}, {-1, INF, 0}}; + assertThrows(IllegalArgumentException.class, () -> JohnsonsAlgorithm.johnsonAlgorithm(graph)); + } + + /** + * Tests Dijkstra's algorithm on a small graph as part of Johnson's Algorithm. + */ + @Test + void testDijkstra() { + double[][] graph = {{0, 1, 2}, {INF, 0, 3}, {INF, INF, 0}}; + double[] modifiedWeights = {0, 0, 0}; + double[] result = JohnsonsAlgorithm.dijkstra(graph, 0, modifiedWeights); + double[] expected = {0, 1, 2}; + assertArrayEquals(expected, result); + } + + /** + * Tests the conversion of an adjacency matrix to an edge list. + */ + @Test + void testEdgeListConversion() { + double[][] graph = {{0, 5, INF}, {INF, 0, 2}, {INF, INF, 0}}; + double[][] edges = JohnsonsAlgorithm.convertToEdgeList(graph); + double[][] expected = {{0, 1, 5}, {1, 2, 2}}; + assertArrayEquals(expected, edges); + } + + /** + * Tests the reweighting of a graph. + */ + @Test + void testReweightGraph() { + double[][] graph = {{0, 2, 9}, {INF, 0, 1}, {INF, INF, 0}}; + double[] modifiedWeights = {1, 2, 3}; + double[][] reweightedGraph = JohnsonsAlgorithm.reweightGraph(graph, modifiedWeights); + double[][] expected = {{0, 1, 7}, {INF, 0, 0}, {INF, INF, 0}}; + assertArrayEquals(expected, reweightedGraph); + } + + /** + * Tests the minDistance method used in Dijkstra's algorithm. + */ + @Test + void testMinDistance() { + double[] dist = {INF, 3, 1, INF}; + boolean[] visited = {false, false, false, false}; + int minIndex = JohnsonsAlgorithm.minDistance(dist, visited); + assertEquals(2, minIndex); + } + + /** + * Tests Johnson's Algorithm on a graph where all vertices are disconnected. + */ + @Test + void testDisconnectedGraph() { + double[][] graph = {{0, INF, INF}, {INF, 0, INF}, {INF, INF, 0}}; + double[][] result = JohnsonsAlgorithm.johnsonAlgorithm(graph); + double[][] expected = {{0, INF, INF}, {INF, 0, INF}, {INF, INF, 0}}; + assertArrayEquals(expected, result); + } + + /** + * Tests Johnson's Algorithm on a fully connected graph. + */ + @Test + void testFullyConnectedGraph() { + double[][] graph = {{0, 1, 2}, {1, 0, 1}, {2, 1, 0}}; + double[][] result = JohnsonsAlgorithm.johnsonAlgorithm(graph); + double[][] expected = {{0, 1, 2}, {1, 0, 1}, {2, 1, 0}}; + assertArrayEquals(expected, result); + } + + /** + * Tests Dijkstra's algorithm on a graph with multiple shortest paths. + */ + @Test + void testDijkstraMultipleShortestPaths() { + double[][] graph = {{0, 1, 2, INF}, {INF, 0, INF, 1}, {INF, INF, 0, 1}, {INF, INF, INF, 0}}; + double[] modifiedWeights = {0, 0, 0, 0}; + double[] result = JohnsonsAlgorithm.dijkstra(graph, 0, modifiedWeights); + double[] expected = {0, 1, 2, 2}; + assertArrayEquals(expected, result); + } + + /** + * Tests Johnson's Algorithm with a graph where all edge weights are zero. + */ + @Test + void testGraphWithZeroWeights() { + double[][] graph = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + double[][] result = JohnsonsAlgorithm.johnsonAlgorithm(graph); + double[][] expected = {{0, INF, INF}, {INF, 0, INF}, {INF, INF, 0}}; + assertArrayEquals(expected, result); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithmTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithmTest.java new file mode 100644 index 000000000000..8d096a4b4459 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithmTest.java @@ -0,0 +1,77 @@ +package com.thealgorithms.datastructures.graphs; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import org.junit.jupiter.api.Test; + +class KahnsAlgorithmTest { + + @Test + void testBasicGraph() { + // Test case with a basic directed acyclic graph (DAG) + AdjacencyList graph = new AdjacencyList<>(); + graph.addEdge("a", "b"); + graph.addEdge("c", "a"); + graph.addEdge("a", "d"); + graph.addEdge("b", "d"); + + TopologicalSort topSort = new TopologicalSort<>(graph); + ArrayList result = topSort.topSortOrder(); + + String[] expectedOrder = {"c", "a", "b", "d"}; + assertArrayEquals(expectedOrder, result.toArray()); + } + + @Test + void testGraphWithMultipleSources() { + // Test case where graph has multiple independent sources + AdjacencyList graph = new AdjacencyList<>(); + graph.addEdge("a", "c"); + graph.addEdge("b", "c"); + + TopologicalSort topSort = new TopologicalSort<>(graph); + ArrayList result = topSort.topSortOrder(); + + String[] expectedOrder = {"a", "b", "c"}; + assertArrayEquals(expectedOrder, result.toArray()); + } + + @Test + void testDisconnectedGraph() { + // Test case for disconnected graph + AdjacencyList graph = new AdjacencyList<>(); + graph.addEdge("a", "b"); + graph.addEdge("c", "d"); + + TopologicalSort topSort = new TopologicalSort<>(graph); + ArrayList result = topSort.topSortOrder(); + + String[] expectedOrder = {"a", "c", "b", "d"}; + assertArrayEquals(expectedOrder, result.toArray()); + } + + @Test + void testGraphWithCycle() { + // Test case for a graph with a cycle - topological sorting is not possible + AdjacencyList graph = new AdjacencyList<>(); + graph.addEdge("a", "b"); + graph.addEdge("b", "c"); + graph.addEdge("c", "a"); + + TopologicalSort topSort = new TopologicalSort<>(graph); + + assertThrows(IllegalStateException.class, () -> topSort.topSortOrder()); + } + + @Test + void testSingleNodeGraph() { + AdjacencyList graph = new AdjacencyList<>(); + graph.addEdge("a", "a"); // self-loop + + TopologicalSort topSort = new TopologicalSort<>(graph); + + assertThrows(IllegalStateException.class, () -> topSort.topSortOrder()); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/KosarajuTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/KosarajuTest.java index c1e68acac2e6..53ed26dff26f 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/KosarajuTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/KosarajuTest.java @@ -1,6 +1,6 @@ package com.thealgorithms.datastructures.graphs; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.ArrayList; import java.util.Arrays; @@ -9,14 +9,13 @@ public class KosarajuTest { - private Kosaraju kosaraju = new Kosaraju(); + private final Kosaraju kosaraju = new Kosaraju(); @Test - public void findStronglyConnectedComps() { - // Create a adjacency list of graph - var n = 8; - var adjList = new ArrayList>(n); - + public void testFindStronglyConnectedComponents() { + // Create a graph using adjacency list + int n = 8; + List> adjList = new ArrayList<>(n); for (int i = 0; i < n; i++) { adjList.add(new ArrayList<>()); } @@ -36,24 +35,24 @@ public void findStronglyConnectedComps() { List> expectedResult = new ArrayList<>(); /* Expected result: - 0, 1, 2 - 3 - 5, 4, 6 - 7 + {0, 1, 2} + {3} + {5, 4, 6} + {7} */ expectedResult.add(Arrays.asList(1, 2, 0)); - expectedResult.add(Arrays.asList(3)); + expectedResult.add(List.of(3)); expectedResult.add(Arrays.asList(5, 6, 4)); - expectedResult.add(Arrays.asList(7)); - assertTrue(expectedResult.equals(actualResult)); + expectedResult.add(List.of(7)); + + assertEquals(expectedResult, actualResult); } @Test - public void findStronglyConnectedCompsShouldGetSingleNodes() { - // Create a adjacency list of graph - var n = 8; - var adjList = new ArrayList>(n); - + public void testFindSingleNodeSCC() { + // Create a simple graph using adjacency list + int n = 8; + List> adjList = new ArrayList<>(n); for (int i = 0; i < n; i++) { adjList.add(new ArrayList<>()); } @@ -71,9 +70,42 @@ public void findStronglyConnectedCompsShouldGetSingleNodes() { List> expectedResult = new ArrayList<>(); /* Expected result: - 0, 1, 2, 3, 4, 5, 6, 7 + {0, 1, 2, 3, 4, 5, 6, 7} */ expectedResult.add(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 0)); - assertTrue(expectedResult.equals(actualResult)); + + assertEquals(expectedResult, actualResult); + } + + @Test + public void testDisconnectedGraph() { + // Create a disconnected graph (two separate components) + int n = 5; + List> adjList = new ArrayList<>(n); + for (int i = 0; i < n; i++) { + adjList.add(new ArrayList<>()); + } + + // Add edges for first component + adjList.get(0).add(1); + adjList.get(1).add(2); + adjList.get(2).add(0); + + // Add edges for second component + adjList.get(3).add(4); + adjList.get(4).add(3); + + List> actualResult = kosaraju.kosaraju(n, adjList); + + List> expectedResult = new ArrayList<>(); + /* + Expected result: + {0, 1, 2} + {3, 4} + */ + expectedResult.add(Arrays.asList(4, 3)); + expectedResult.add(Arrays.asList(1, 2, 0)); + + assertEquals(expectedResult, actualResult); } } diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/KruskalTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/KruskalTest.java new file mode 100644 index 000000000000..b18f161ef1a6 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/graphs/KruskalTest.java @@ -0,0 +1,112 @@ +package com.thealgorithms.datastructures.graphs; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class KruskalTest { + + private Kruskal kruskal; + private HashSet[] graph; + + @BeforeEach + public void setUp() { + kruskal = new Kruskal(); + int n = 7; + graph = new HashSet[n]; + for (int i = 0; i < n; i++) { + graph[i] = new HashSet<>(); + } + + // Add edges to the graph + Kruskal.addEdge(graph, 0, 1, 2); + Kruskal.addEdge(graph, 0, 2, 3); + Kruskal.addEdge(graph, 0, 3, 3); + Kruskal.addEdge(graph, 1, 2, 4); + Kruskal.addEdge(graph, 2, 3, 5); + Kruskal.addEdge(graph, 1, 4, 3); + Kruskal.addEdge(graph, 2, 4, 1); + Kruskal.addEdge(graph, 3, 5, 7); + Kruskal.addEdge(graph, 4, 5, 8); + Kruskal.addEdge(graph, 5, 6, 9); + } + + @Test + public void testKruskal() { + int n = 6; + HashSet[] graph = new HashSet[n]; + + for (int i = 0; i < n; i++) { + graph[i] = new HashSet<>(); + } + + Kruskal.addEdge(graph, 0, 1, 4); + Kruskal.addEdge(graph, 0, 2, 2); + Kruskal.addEdge(graph, 1, 2, 1); + Kruskal.addEdge(graph, 1, 3, 5); + Kruskal.addEdge(graph, 2, 3, 8); + Kruskal.addEdge(graph, 2, 4, 10); + Kruskal.addEdge(graph, 3, 4, 2); + Kruskal.addEdge(graph, 3, 5, 6); + Kruskal.addEdge(graph, 4, 5, 3); + + HashSet[] result = kruskal.kruskal(graph); + + List> actualEdges = new ArrayList<>(); + for (HashSet edges : result) { + for (Kruskal.Edge edge : edges) { + actualEdges.add(Arrays.asList(edge.from, edge.to, edge.weight)); + } + } + + List> expectedEdges = Arrays.asList(Arrays.asList(1, 2, 1), Arrays.asList(0, 2, 2), Arrays.asList(3, 4, 2), Arrays.asList(4, 5, 3), Arrays.asList(1, 3, 5)); + + assertTrue(actualEdges.containsAll(expectedEdges) && expectedEdges.containsAll(actualEdges)); + } + + @Test + public void testEmptyGraph() { + HashSet[] emptyGraph = new HashSet[0]; + HashSet[] result = kruskal.kruskal(emptyGraph); + assertEquals(0, result.length); + } + + @Test + public void testSingleNodeGraph() { + HashSet[] singleNodeGraph = new HashSet[1]; + singleNodeGraph[0] = new HashSet<>(); + HashSet[] result = kruskal.kruskal(singleNodeGraph); + assertTrue(result[0].isEmpty()); + } + + @Test + public void testGraphWithDisconnectedNodes() { + int n = 5; + HashSet[] disconnectedGraph = new HashSet[n]; + for (int i = 0; i < n; i++) { + disconnectedGraph[i] = new HashSet<>(); + } + + Kruskal.addEdge(disconnectedGraph, 0, 1, 2); + Kruskal.addEdge(disconnectedGraph, 2, 3, 4); + + HashSet[] result = kruskal.kruskal(disconnectedGraph); + + List> actualEdges = new ArrayList<>(); + for (HashSet edges : result) { + for (Kruskal.Edge edge : edges) { + actualEdges.add(Arrays.asList(edge.from, edge.to, edge.weight)); + } + } + + List> expectedEdges = Arrays.asList(Arrays.asList(0, 1, 2), Arrays.asList(2, 3, 4)); + + assertTrue(actualEdges.containsAll(expectedEdges) && expectedEdges.containsAll(actualEdges)); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/MatrixGraphsTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/MatrixGraphsTest.java new file mode 100644 index 000000000000..cc8a2df872ce --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/graphs/MatrixGraphsTest.java @@ -0,0 +1,140 @@ +package com.thealgorithms.datastructures.graphs; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +class MatrixGraphsTest { + + @Test + void testGraphConstruction() { + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(5); + assertEquals(5, graph.numberOfVertices()); + assertEquals(0, graph.numberOfEdges()); + } + + @Test + void testAddEdge() { + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(5); + assertTrue(graph.addEdge(0, 1)); + assertTrue(graph.edgeDoesExist(0, 1)); + assertTrue(graph.edgeDoesExist(1, 0)); + assertEquals(1, graph.numberOfEdges()); + + // Adding the same edge again should return false + assertFalse(graph.addEdge(0, 1)); + assertFalse(graph.addEdge(5, 1)); + assertFalse(graph.addEdge(-1, 1)); + } + + @Test + void testRemoveEdge() { + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(5); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + + assertTrue(graph.removeEdge(0, 1)); + assertFalse(graph.edgeDoesExist(0, 1)); + assertFalse(graph.edgeDoesExist(1, 0)); + assertEquals(1, graph.numberOfEdges()); + + assertFalse(graph.removeEdge(0, 3)); + assertFalse(graph.removeEdge(5, 1)); + assertFalse(graph.removeEdge(-1, 1)); + } + + @Test + void testVertexDoesExist() { + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(5); + assertTrue(graph.vertexDoesExist(0)); + assertTrue(graph.vertexDoesExist(4)); + assertFalse(graph.vertexDoesExist(5)); + assertFalse(graph.vertexDoesExist(-1)); + } + + @Test + void testDepthFirstOrder() { + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(5); + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(1, 3); + graph.addEdge(2, 4); + + List dfs = graph.depthFirstOrder(0); + assertEquals(5, dfs.size()); + assertEquals(0, dfs.getFirst()); + + assertTrue(dfs.containsAll(Arrays.asList(0, 1, 2, 3, 4))); + + List emptyDfs = graph.depthFirstOrder(5); + assertTrue(emptyDfs.isEmpty()); + } + + @Test + void testBreadthFirstOrder() { + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(5); + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(1, 3); + graph.addEdge(2, 4); + + List bfs = graph.breadthFirstOrder(0); + assertEquals(5, bfs.size()); + assertEquals(0, bfs.getFirst()); + + assertTrue(bfs.containsAll(Arrays.asList(0, 1, 2, 3, 4))); + + List emptyBfs = graph.breadthFirstOrder(5); + assertTrue(emptyBfs.isEmpty()); + } + + @Test + void testToString() { + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(3); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + + String expected = " 0 1 2 \n" + + "0 : 0 1 0 \n" + + "1 : 1 0 1 \n" + + "2 : 0 1 0 \n"; + + assertEquals(expected, graph.toString()); + } + + @Test + void testCyclicGraph() { + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(4); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + graph.addEdge(2, 3); + graph.addEdge(3, 0); + + 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 testDisconnectedGraph() { + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(5); + graph.addEdge(0, 1); + graph.addEdge(2, 3); + + List dfs = graph.depthFirstOrder(0); + List bfs = graph.breadthFirstOrder(0); + + assertEquals(2, dfs.size()); + assertEquals(2, bfs.size()); + assertTrue(dfs.containsAll(Arrays.asList(0, 1))); + assertTrue(bfs.containsAll(Arrays.asList(0, 1))); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/PrimMSTTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/PrimMSTTest.java new file mode 100644 index 000000000000..ec59a3880642 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/graphs/PrimMSTTest.java @@ -0,0 +1,54 @@ +package com.thealgorithms.datastructures.graphs; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.Test; + +public class PrimMSTTest { + + private final PrimMST primMST = new PrimMST(); + + @Test + public void testSimpleGraph() { + // Test graph with 5 nodes and weighted edges + int[][] graph = {{0, 2, 0, 6, 0}, {2, 0, 3, 8, 5}, {0, 3, 0, 0, 7}, {6, 8, 0, 0, 9}, {0, 5, 7, 9, 0}}; + + int[] expectedParent = {-1, 0, 1, 0, 1}; + int[] actualParent = primMST.primMST(graph); + + assertArrayEquals(expectedParent, actualParent); + } + + @Test + public void testDisconnectedGraph() { + // Test case with a disconnected graph (no valid MST) + int[][] graph = {{0, 1, 0, 0, 0}, {1, 0, 2, 0, 0}, {0, 2, 0, 3, 0}, {0, 0, 3, 0, 4}, {0, 0, 0, 4, 0}}; + + int[] expectedParent = {-1, 0, 1, 2, 3}; // Expected MST parent array + int[] actualParent = primMST.primMST(graph); + + assertArrayEquals(expectedParent, actualParent); + } + + @Test + public void testAllEqualWeightsGraph() { + // Test case where all edges have equal weight + int[][] graph = {{0, 1, 1, 1, 1}, {1, 0, 1, 1, 1}, {1, 1, 0, 1, 1}, {1, 1, 1, 0, 1}, {1, 1, 1, 1, 0}}; + + int[] expectedParent = {-1, 0, 0, 0, 0}; // Expected MST parent array (any valid spanning tree) + int[] actualParent = primMST.primMST(graph); + + assertArrayEquals(expectedParent, actualParent); + } + + @Test + public void testSparseGraph() { + // Test case with a sparse graph (few edges) + int[][] graph = {{0, 1, 0, 0, 0}, {1, 0, 1, 0, 0}, {0, 1, 0, 1, 0}, {0, 0, 1, 0, 1}, {0, 0, 0, 1, 0}}; + + int[] expectedParent = {-1, 0, 1, 2, 3}; // Expected MST parent array + int[] actualParent = primMST.primMST(graph); + + assertArrayEquals(expectedParent, actualParent); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithmTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithmTest.java index dc81d99dd0bf..314cc415815d 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithmTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithmTest.java @@ -1,6 +1,6 @@ package com.thealgorithms.datastructures.graphs; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.ArrayList; import java.util.Arrays; @@ -9,11 +9,11 @@ public class TarjansAlgorithmTest { - TarjansAlgorithm tarjansAlgo = new TarjansAlgorithm(); + private final TarjansAlgorithm tarjansAlgo = new TarjansAlgorithm(); @Test - public void findStronglyConnectedComps() { - var v = 5; + public void testFindStronglyConnectedComponents() { + int v = 5; var graph = new ArrayList>(); for (int i = 0; i < v; i++) { graph.add(new ArrayList<>()); @@ -32,23 +32,20 @@ public void findStronglyConnectedComps() { 4 */ List> expectedResult = new ArrayList<>(); - - expectedResult.add(Arrays.asList(4)); - expectedResult.add(Arrays.asList(3)); + expectedResult.add(List.of(4)); + expectedResult.add(List.of(3)); expectedResult.add(Arrays.asList(2, 1, 0)); - assertTrue(expectedResult.equals(actualResult)); + assertEquals(expectedResult, actualResult); } @Test - public void findStronglyConnectedCompsShouldGetSingleNodes() { - // Create a adjacency list of graph - var n = 8; + public void testFindStronglyConnectedComponentsWithSingleNodes() { + // Create a graph where each node is its own SCC + int n = 8; var adjList = new ArrayList>(n); - for (int i = 0; i < n; i++) { adjList.add(new ArrayList<>()); } - adjList.get(0).add(1); adjList.get(1).add(2); adjList.get(2).add(3); @@ -65,6 +62,71 @@ public void findStronglyConnectedCompsShouldGetSingleNodes() { 7, 6, 5, 4, 3, 2, 1, 0 */ expectedResult.add(Arrays.asList(7, 6, 5, 4, 3, 2, 1, 0)); - assertTrue(expectedResult.equals(actualResult)); + assertEquals(expectedResult, actualResult); + } + + @Test + public void testGraphWithMultipleSCCs() { + int v = 6; + var graph = new ArrayList>(); + for (int i = 0; i < v; i++) { + graph.add(new ArrayList<>()); + } + graph.get(0).add(1); + graph.get(1).add(2); + graph.get(2).add(0); + graph.get(3).add(4); + graph.get(4).add(5); + graph.get(5).add(3); + + var actualResult = tarjansAlgo.stronglyConnectedComponents(v, graph); + List> expectedResult = new ArrayList<>(); + expectedResult.add(Arrays.asList(2, 1, 0)); // SCC containing 0, 1, 2 + expectedResult.add(Arrays.asList(5, 4, 3)); // SCC containing 3, 4, 5 + assertEquals(expectedResult, actualResult); + } + + @Test + public void testDisconnectedGraph() { + int v = 7; + var graph = new ArrayList>(); + for (int i = 0; i < v; i++) { + graph.add(new ArrayList<>()); + } + graph.get(0).add(1); + graph.get(1).add(0); + graph.get(2).add(3); + graph.get(3).add(4); + graph.get(4).add(2); + + var actualResult = tarjansAlgo.stronglyConnectedComponents(v, graph); + List> expectedResult = new ArrayList<>(); + expectedResult.add(Arrays.asList(1, 0)); // SCC containing 0, 1 + expectedResult.add(Arrays.asList(4, 3, 2)); // SCC containing 2, 3, 4 + expectedResult.add(List.of(5)); // SCC containing 5 + expectedResult.add(List.of(6)); // SCC containing 6 + assertEquals(expectedResult, actualResult); + } + + @Test + public void testSingleNodeGraph() { + int v = 1; + var graph = new ArrayList>(); + graph.add(new ArrayList<>()); + + var actualResult = tarjansAlgo.stronglyConnectedComponents(v, graph); + List> expectedResult = new ArrayList<>(); + expectedResult.add(List.of(0)); // SCC with a single node + assertEquals(expectedResult, actualResult); + } + + @Test + public void testEmptyGraph() { + int v = 0; + var graph = new ArrayList>(); + + var actualResult = tarjansAlgo.stronglyConnectedComponents(v, graph); + List> expectedResult = new ArrayList<>(); // No SCCs in an empty graph + assertEquals(expectedResult, actualResult); } } diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/WelshPowellTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/WelshPowellTest.java index b37657db5c05..f45c4e10be56 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/WelshPowellTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/WelshPowellTest.java @@ -34,26 +34,25 @@ void testCompleteGraph() { assertEquals(3, countDistinctColors(colors)); } - // The following test originates from the following website : https://www.geeksforgeeks.org/welsh-powell-graph-colouring-algorithm/ @Test void testComplexGraph() { int[][] edges = { - {0, 7}, // A-H - {0, 1}, // A-B - {1, 3}, // B-D - {2, 3}, // C-D - {3, 8}, // D-I - {3, 10}, // D-K - {4, 10}, // E-K - {4, 5}, // E-F - {5, 6}, // F-G - {6, 10}, // G-K - {6, 7}, // G-H - {7, 8}, // H-I - {7, 9}, // H-J - {7, 10}, // H-K - {8, 9}, // I-J - {9, 10}, // J-K + {0, 7}, + {0, 1}, + {1, 3}, + {2, 3}, + {3, 8}, + {3, 10}, + {4, 10}, + {4, 5}, + {5, 6}, + {6, 10}, + {6, 7}, + {7, 8}, + {7, 9}, + {7, 10}, + {8, 9}, + {9, 10}, }; final var graph = WelshPowell.makeGraph(11, edges); // 11 vertices from A (0) to K (10) @@ -86,24 +85,35 @@ void testInvalidEdgeArray() { @Test void testWithPreColoredVertex() { - // Create a linear graph with 4 vertices and edges connecting them in sequence final var graph = WelshPowell.makeGraph(4, new int[][] {{0, 1}, {1, 2}, {2, 3}}); - - // Apply the Welsh-Powell coloring algorithm to the graph int[] colors = WelshPowell.findColoring(graph); - - // Validate that the coloring is correct (no two adjacent vertices have the same color) assertTrue(isColoringValid(graph, colors)); - - // Check if the algorithm has used at least 2 colors (expected for a linear graph) assertTrue(countDistinctColors(colors) >= 2); - - // Verify that all vertices have been assigned a color for (int color : colors) { assertTrue(color >= 0); } } + @Test + void testLargeGraph() { + int[][] edges = {{0, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 5}, {5, 0}, {6, 7}, {7, 8}, {8, 6}, {9, 10}, {10, 11}, {11, 9}, {12, 13}, {13, 14}, {14, 15}}; + + final var graph = WelshPowell.makeGraph(16, edges); // 16 vertices + int[] colors = WelshPowell.findColoring(graph); + assertTrue(isColoringValid(graph, colors)); + assertEquals(3, countDistinctColors(colors)); // Expecting a maximum of 3 colors + } + + @Test + void testStarGraph() { + int[][] edges = {{0, 1}, {0, 2}, {0, 3}, {0, 4}}; + + final var graph = WelshPowell.makeGraph(5, edges); // 5 vertices in a star formation + int[] colors = WelshPowell.findColoring(graph); + assertTrue(isColoringValid(graph, colors)); + assertEquals(2, countDistinctColors(colors)); // Star graph can be colored with 2 colors + } + private boolean isColoringValid(Graph graph, int[] colors) { if (Arrays.stream(colors).anyMatch(n -> n < 0)) { return false; diff --git a/src/test/java/com/thealgorithms/datastructures/hashmap/HashMapCuckooHashingTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/HashMapCuckooHashingTest.java deleted file mode 100644 index 14bddeae1c91..000000000000 --- a/src/test/java/com/thealgorithms/datastructures/hashmap/HashMapCuckooHashingTest.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.thealgorithms.datastructures.hashmap; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import com.thealgorithms.datastructures.hashmap.hashing.HashMapCuckooHashing; -import org.junit.jupiter.api.Test; - -class HashMapCuckooHashingTest { - - @Test - void insertKey() { - HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10); - assertEquals(0, hashTable.getNumberOfKeysInTable()); - - hashTable.insertKey2HashTable(3); - - assertEquals(1, hashTable.getNumberOfKeysInTable()); - } - - @Test - void getKeyIndex() { - HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10); - hashTable.insertKey2HashTable(8); - hashTable.insertKey2HashTable(4); - - assertNotEquals(-1, hashTable.findKeyInTable(8)); - } - - @Test - void containsKey() { - HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10); - hashTable.insertKey2HashTable(8); - boolean contains = hashTable.checkTableContainsKey(8); - - assertTrue(contains); - } - - @Test - void removeKey() { - HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10); - hashTable.insertKey2HashTable(3); - - int initialSize = hashTable.getNumberOfKeysInTable(); - - hashTable.deleteKeyFromHashTable(3); - - assertEquals(initialSize - 1, hashTable.getNumberOfKeysInTable()); - } - - @Test - void removeNone() { - HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10); - try { - hashTable.deleteKeyFromHashTable(3); - } catch (Exception e) { - assertTrue(true); - return; - } - fail(); - } - - @Test - void reHashTableIncreasesTableSize() { - HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10); - int initialSize = hashTable.getNumberOfKeysInTable(); - - hashTable.reHashTableIncreasesTableSize(); - - assertEquals(initialSize * 2, hashTable.getNumberOfKeysInTable()); - } - - @Test - void hashFunctionsAreDifferent() { - HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10); - hashTable.insertKey2HashTable(33); - - assertNotEquals(hashTable.hashFunction1(3), hashTable.hashFunction2(3)); - } - - @Test - void avoidInfiniteLoops() { - HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10); - hashTable.insertKey2HashTable(0); - hashTable.insertKey2HashTable(10); - hashTable.insertKey2HashTable(100); - - assertTrue(hashTable.checkTableContainsKey(0)); - assertTrue(hashTable.checkTableContainsKey(10)); - assertTrue(hashTable.checkTableContainsKey(100)); - } -} diff --git a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayListTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayListTest.java index 37e43d2aada3..629aaae95753 100644 --- a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayListTest.java +++ b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayListTest.java @@ -50,4 +50,47 @@ void testGenericHashmapWhichUsesArrayAndKeyIsIntegerValueIsString() { assertEquals("Washington DC", map.get(101)); assertTrue(map.containsKey(46)); } + + @Test + void testRemoveNonExistentKey() { + GenericHashMapUsingArrayList map = new GenericHashMapUsingArrayList<>(); + map.put("USA", "Washington DC"); + map.remove("Nepal"); // Attempting to remove a non-existent key + assertEquals(1, map.size()); // Size should remain the same + } + + @Test + void testRehashing() { + GenericHashMapUsingArrayList map = new GenericHashMapUsingArrayList<>(); + 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 + } + + @Test + void testUpdateValueForExistingKey() { + GenericHashMapUsingArrayList map = new GenericHashMapUsingArrayList<>(); + map.put("USA", "Washington DC"); + map.put("USA", "New Washington DC"); // Updating value for existing key + assertEquals("New Washington DC", map.get("USA")); + } + + @Test + void testToStringMethod() { + GenericHashMapUsingArrayList map = new GenericHashMapUsingArrayList<>(); + map.put("USA", "Washington DC"); + map.put("Nepal", "Kathmandu"); + String expected = "{USA : Washington DC, Nepal : Kathmandu}"; + assertEquals(expected, map.toString()); + } + + @Test + void testContainsKey() { + GenericHashMapUsingArrayList map = new GenericHashMapUsingArrayList<>(); + map.put("USA", "Washington DC"); + assertTrue(map.containsKey("USA")); + assertFalse(map.containsKey("Nepal")); + } } 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 483e43bb5cb3..5d1733a3e97c 100644 --- a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java +++ b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java @@ -50,4 +50,47 @@ void testGenericHashmapWhichUsesArrayAndKeyIsIntegerValueIsString() { assertEquals("Washington DC", map.get(101)); assertTrue(map.containsKey(46)); } + + @Test + 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 + } + + @Test + void testRehashing() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + 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 + } + + @Test + 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")); + } + + @Test + void testToStringMethod() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + map.put("USA", "Washington DC"); + map.put("Nepal", "Kathmandu"); + String expected = "{USA : Washington DC, Nepal : Kathmandu}"; + assertEquals(expected, map.toString()); + } + + @Test + void testContainsKey() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + map.put("USA", "Washington DC"); + assertTrue(map.containsKey("USA")); + assertFalse(map.containsKey("Nepal")); + } } diff --git a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashingTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashingTest.java new file mode 100644 index 000000000000..c2f80bfe3210 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashingTest.java @@ -0,0 +1,140 @@ +package com.thealgorithms.datastructures.hashmap.hashing; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class HashMapCuckooHashingTest { + + @Test + void insertKey() { + HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10); + assertEquals(0, hashTable.getNumberOfKeysInTable()); + + hashTable.insertKey2HashTable(3); + assertEquals(1, hashTable.getNumberOfKeysInTable()); + } + + @Test + void getKeyIndex() { + HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10); + hashTable.insertKey2HashTable(8); + hashTable.insertKey2HashTable(4); + + assertNotEquals(-1, hashTable.findKeyInTable(8)); + } + + @Test + void containsKey() { + HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10); + hashTable.insertKey2HashTable(8); + boolean contains = hashTable.checkTableContainsKey(8); + + assertTrue(contains); + } + + @Test + void removeKey() { + HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10); + hashTable.insertKey2HashTable(3); + + int initialSize = hashTable.getNumberOfKeysInTable(); + hashTable.deleteKeyFromHashTable(3); + + assertEquals(initialSize - 1, hashTable.getNumberOfKeysInTable()); + } + + @Test + void removeNone() { + HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10); + try { + hashTable.deleteKeyFromHashTable(3); + } catch (Exception e) { + assertTrue(true); + return; + } + Assertions.fail("Expected exception when trying to delete a non-existing key."); + } + + @Test + void reHashTableIncreasesTableSize() { + HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10); + hashTable.insertKey2HashTable(1); + hashTable.insertKey2HashTable(2); + hashTable.insertKey2HashTable(3); + hashTable.insertKey2HashTable(4); + hashTable.insertKey2HashTable(5); + hashTable.insertKey2HashTable(6); + hashTable.insertKey2HashTable(7); + hashTable.insertKey2HashTable(8); + hashTable.insertKey2HashTable(9); + hashTable.insertKey2HashTable(10); // This should trigger rehashing + + assertEquals(10, hashTable.getNumberOfKeysInTable()); + } + + @Test + void hashFunctionsAreDifferent() { + HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10); + hashTable.insertKey2HashTable(33); + + assertNotEquals(hashTable.hashFunction1(3), hashTable.hashFunction2(3), "Hash functions should produce different indices."); + } + + @Test + void avoidInfiniteLoops() { + HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10); + hashTable.insertKey2HashTable(0); + hashTable.insertKey2HashTable(10); + hashTable.insertKey2HashTable(100); + + assertTrue(hashTable.checkTableContainsKey(0)); + assertTrue(hashTable.checkTableContainsKey(10)); + assertTrue(hashTable.checkTableContainsKey(100)); + } + + @Test + void testLoadFactor() { + HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10); + for (int i = 1; i <= 8; i++) { // Insert 8 keys to test load factor + hashTable.insertKey2HashTable(i); + } + assertEquals(8, hashTable.getNumberOfKeysInTable()); + // Check that rehashing occurs when a 9th key is added + hashTable.insertKey2HashTable(9); + assertTrue(hashTable.getNumberOfKeysInTable() > 8, "Load factor exceeded, table should have been resized."); + } + + @Test + void testDeleteNonExistentKey() { + HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10); + hashTable.insertKey2HashTable(1); + hashTable.insertKey2HashTable(2); + + Exception exception = null; + try { + hashTable.deleteKeyFromHashTable(3); // Try deleting a non-existent key + } catch (IllegalArgumentException e) { + exception = e; // Capture the exception + } + assertNotNull(exception, "Expected an IllegalArgumentException when deleting a non-existent key."); + } + + @Test + void testInsertDuplicateKey() { + HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10); + hashTable.insertKey2HashTable(1); + Exception exception = null; + + try { + hashTable.insertKey2HashTable(1); // Attempt to insert duplicate key + } catch (IllegalArgumentException e) { + exception = e; // Capture the exception + } + assertNotNull(exception, "Expected an IllegalArgumentException for duplicate key insertion."); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java index 3552bc1aa9c5..ff3ba3ed2571 100644 --- a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java +++ b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java @@ -17,7 +17,7 @@ public void testInsertAndSearch() { assertEquals("Value15", hashMap.search(15)); assertEquals("Value25", hashMap.search(25)); assertEquals("Value35", hashMap.search(35)); - assertNull(hashMap.search(45)); + assertNull(hashMap.search(45)); // Test for non-existent key } @Test @@ -29,7 +29,7 @@ public void testDelete() { assertEquals("Value25", hashMap.search(25)); hashMap.delete(25); - assertNull(hashMap.search(25)); + assertNull(hashMap.search(25)); // Confirm deletion } @Test @@ -38,21 +38,22 @@ public void testDisplay() { hashMap.insert(15, "Value15"); hashMap.insert(25, "Value25"); hashMap.insert(35, "Value35"); - hashMap.display(); + // Optionally verify display functionality if it returns a string + hashMap.display(); // Manual check during test execution } @Test public void testInsertNullKey() { HashMap hashMap = new HashMap<>(10); hashMap.insert(null, "NullValue"); - assertEquals("NullValue", hashMap.search(null)); + assertEquals("NullValue", hashMap.search(null)); // Verify null key handling } @Test public void testInsertNullValue() { HashMap hashMap = new HashMap<>(10); hashMap.insert(15, null); - assertNull(hashMap.search(15)); + assertNull(hashMap.search(15)); // Verify null value handling } @Test @@ -61,12 +62,12 @@ public void testUpdateExistingKey() { hashMap.insert(15, "Value15"); hashMap.insert(15, "UpdatedValue15"); - assertEquals("UpdatedValue15", hashMap.search(15)); + assertEquals("UpdatedValue15", hashMap.search(15)); // Verify update } @Test public void testHandleCollisions() { - HashMap hashMap = new HashMap<>(3); + HashMap hashMap = new HashMap<>(3); // Create a small bucket size to force collisions // These keys should collide if the hash function is modulo 3 hashMap.insert(1, "Value1"); hashMap.insert(4, "Value4"); @@ -80,17 +81,17 @@ public void testHandleCollisions() { @Test public void testSearchInEmptyHashMap() { HashMap hashMap = new HashMap<>(10); - assertNull(hashMap.search(10)); + assertNull(hashMap.search(10)); // Confirm search returns null in empty map } @Test public void testDeleteNonExistentKey() { HashMap hashMap = new HashMap<>(10); hashMap.insert(15, "Value15"); - hashMap.delete(25); + hashMap.delete(25); // Delete non-existent key - assertEquals("Value15", hashMap.search(15)); - assertNull(hashMap.search(25)); + assertEquals("Value15", hashMap.search(15)); // Ensure existing key remains + assertNull(hashMap.search(25)); // Confirm non-existent key remains null } @Test @@ -101,7 +102,7 @@ public void testInsertLargeNumberOfElements() { } for (int i = 0; i < 100; i++) { - assertEquals("Value" + i, hashMap.search(i)); + assertEquals("Value" + i, hashMap.search(i)); // Verify all inserted values } } @@ -113,7 +114,7 @@ public void testDeleteHeadOfBucket() { hashMap.insert(7, "Value7"); hashMap.delete(1); - assertNull(hashMap.search(1)); + assertNull(hashMap.search(1)); // Verify head deletion assertEquals("Value4", hashMap.search(4)); assertEquals("Value7", hashMap.search(7)); } @@ -126,7 +127,7 @@ public void testDeleteTailOfBucket() { hashMap.insert(7, "Value7"); hashMap.delete(7); - assertNull(hashMap.search(7)); + assertNull(hashMap.search(7)); // Verify tail deletion assertEquals("Value1", hashMap.search(1)); assertEquals("Value4", hashMap.search(4)); } @@ -139,8 +140,45 @@ public void testDeleteMiddleElementOfBucket() { hashMap.insert(7, "Value7"); hashMap.delete(4); - assertNull(hashMap.search(4)); + assertNull(hashMap.search(4)); // Verify middle element deletion assertEquals("Value1", hashMap.search(1)); assertEquals("Value7", hashMap.search(7)); } + + @Test + public void testResizeHashMap() { + HashMap hashMap = new HashMap<>(2); // Small initial size to force rehashing + for (int i = 0; i < 10; i++) { + hashMap.insert(i, "Value" + i); + } + + // Verify all values after resizing + for (int i = 0; i < 10; i++) { + assertEquals("Value" + i, hashMap.search(i)); + } + } + + @Test + public void testCollisionResolution() { + HashMap hashMap = new HashMap<>(3); + hashMap.insert("abc", "Value1"); // Hash index 0 + hashMap.insert("cab", "Value2"); // Hash index 0 (collision) + hashMap.insert("bac", "Value3"); // Hash index 0 (collision) + + assertEquals("Value1", hashMap.search("abc")); + assertEquals("Value2", hashMap.search("cab")); + assertEquals("Value3", hashMap.search("bac")); + } + + @Test + public void testClearHashMap() { + HashMap hashMap = new HashMap<>(10); + hashMap.insert(1, "Value1"); + hashMap.insert(2, "Value2"); + + hashMap.clear(); // Assuming clear method resets the hash map + assertNull(hashMap.search(1)); + assertNull(hashMap.search(2)); + assertEquals(0, hashMap.size()); // Verify size is reset + } } diff --git a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/IntersectionTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/IntersectionTest.java new file mode 100644 index 000000000000..df6d15fd9ba4 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/IntersectionTest.java @@ -0,0 +1,76 @@ +package com.thealgorithms.datastructures.hashmap.hashing; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.Test; + +public class IntersectionTest { + + @Test + void testBasicIntersection() { + int[] arr1 = {1, 2, 2, 1}; + int[] arr2 = {2, 2}; + List result = Intersection.intersection(arr1, arr2); + assertEquals(List.of(2, 2), result, "Intersection should return [2, 2]"); + } + + @Test + void testNoIntersection() { + int[] arr1 = {1, 2, 3}; + int[] arr2 = {4, 5, 6}; + List result = Intersection.intersection(arr1, arr2); + assertTrue(result.isEmpty(), "Intersection should be empty for disjoint sets"); + } + + @Test + void testEmptyArray() { + int[] arr1 = {}; + int[] arr2 = {1, 2, 3}; + List result = Intersection.intersection(arr1, arr2); + assertTrue(result.isEmpty(), "Intersection should be empty when first array is empty"); + + result = Intersection.intersection(arr2, arr1); + assertTrue(result.isEmpty(), "Intersection should be empty when second array is empty"); + } + + @Test + void testNullArray() { + int[] arr1 = null; + int[] arr2 = {1, 2, 3}; + List result = Intersection.intersection(arr1, arr2); + assertTrue(result.isEmpty(), "Intersection should be empty when first array is null"); + + result = Intersection.intersection(arr2, arr1); + assertTrue(result.isEmpty(), "Intersection should be empty when second array is null"); + } + + @Test + void testMultipleOccurrences() { + int[] arr1 = {5, 5, 5, 6}; + int[] arr2 = {5, 5, 6, 6, 6}; + List result = Intersection.intersection(arr1, arr2); + assertEquals(List.of(5, 5, 6), result, "Intersection should return [5, 5, 6]"); + } + + @Test + void testSameElements() { + int[] arr1 = {1, 1, 1}; + int[] arr2 = {1, 1, 1}; + List result = Intersection.intersection(arr1, arr2); + assertEquals(List.of(1, 1, 1), result, "Intersection should return [1, 1, 1] for same elements"); + } + + @Test + void testLargeArrays() { + int[] arr1 = new int[1000]; + int[] arr2 = new int[1000]; + for (int i = 0; i < 1000; i++) { + arr1[i] = i; + arr2[i] = i; + } + List result = Intersection.intersection(arr1, arr2); + assertEquals(1000, result.size(), "Intersection should return all elements for identical large arrays"); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMapTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMapTest.java index d0a72a1509ee..34b165d4bbcf 100644 --- a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMapTest.java +++ b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMapTest.java @@ -1,8 +1,91 @@ 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.assertTrue; + +import org.junit.jupiter.api.Test; + class LinearProbingHashMapTest extends MapTest { + @Override , Value> Map getMap() { return new LinearProbingHashMap<>(); } + + @Test + void putNullKey() { + Map map = getMap(); + assertFalse(map.put(null, "value"), "Putting a null key should return false"); + } + + @Test + void putDuplicateKeys() { + Map map = getMap(); + map.put(1, "one"); + map.put(1, "uno"); + assertEquals("uno", map.get(1), "Value should be updated to 'uno'"); + } + + @Test + void putResizeTest() { + Map map = getMap(); + for (int i = 0; i < 20; i++) { + map.put(i, String.valueOf(i)); + } + assertEquals(20, map.size(), "Map size should be 20 after inserting 20 elements"); + } + + @Test + void deleteNonExistentKey() { + Map map = getMap(); + assertFalse(map.delete(999), "Deleting a non-existent key should return false"); + } + + @Test + void deleteAndReinsert() { + Map map = getMap(); + map.put(1, "one"); + map.delete(1); + assertFalse(map.contains(1), "Map should not contain the deleted key"); + map.put(1, "one again"); + assertTrue(map.contains(1), "Map should contain the key after reinsertion"); + } + + @Test + void resizeDown() { + Map map = getMap(); + for (int i = 0; i < 16; i++) { + map.put(i, String.valueOf(i)); + } + for (int i = 0; i < 12; i++) { + map.delete(i); + } + assertEquals(4, map.size(), "Map size should be 4 after deleting 12 elements"); + } + + @Test + void keysOrderTest() { + Map map = getMap(); + for (int i = 10; i > 0; i--) { + map.put(i, String.valueOf(i)); + } + int expectedKey = 1; + for (Integer key : map.keys()) { + assertEquals(expectedKey++, key, "Keys should be in sorted order"); + } + } + + @Test + void stressTest() { + Map map = getMap(); + for (int i = 0; i < 1000; i++) { + map.put(i, String.valueOf(i)); + assertEquals(i + 1, map.size(), "Size should match number of inserted elements"); + } + for (int i = 0; i < 500; i++) { + map.delete(i); + assertEquals(1000 - (i + 1), map.size(), "Size should decrease correctly"); + } + } } diff --git a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElementTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElementTest.java index 49133ba5ffb5..7dcd5eb7a8f4 100644 --- a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElementTest.java +++ b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElementTest.java @@ -42,4 +42,39 @@ void testMajorityWithEmptyArray() { List actual = MajorityElement.majority(nums); assertEquals(expected, actual); } + + @Test + void testMajorityWithAllElementsSame() { + int[] nums = {5, 5, 5, 5, 5}; + List expected = new ArrayList<>(); + expected.add(5); + List actual = MajorityElement.majority(nums); + assertEquals(expected, actual); + } + + @Test + void testMajorityWithEvenCountAndOneMajorityElement() { + int[] nums = {1, 2, 2, 3, 3, 2}; + List expected = new ArrayList<>(); + expected.add(2); + List actual = MajorityElement.majority(nums); + assertEquals(expected, actual); + } + + @Test + void testMajorityWithNoElementsEqualToHalf() { + int[] nums = {1, 1, 2, 2, 3, 3, 4}; + List expected = Collections.emptyList(); + List actual = MajorityElement.majority(nums); + assertEquals(expected, actual); + } + + @Test + void testMajorityWithLargeArray() { + int[] nums = {1, 2, 3, 1, 1, 1, 2, 1, 1}; + List expected = new ArrayList<>(); + expected.add(1); + List actual = MajorityElement.majority(nums); + assertEquals(expected, actual); + } } diff --git a/src/test/java/com/thealgorithms/datastructures/heaps/FibonacciHeapTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/FibonacciHeapTest.java index b414bab2b8a0..d911f3ac30d8 100644 --- a/src/test/java/com/thealgorithms/datastructures/heaps/FibonacciHeapTest.java +++ b/src/test/java/com/thealgorithms/datastructures/heaps/FibonacciHeapTest.java @@ -6,7 +6,7 @@ public class FibonacciHeapTest { @Test - void testHeap() { + void testHeapInsertionAndMinimum() { FibonacciHeap fibonacciHeap = new FibonacciHeap(); fibonacciHeap.insert(5); fibonacciHeap.insert(3); @@ -14,8 +14,95 @@ void testHeap() { fibonacciHeap.insert(18); fibonacciHeap.insert(33); - Assertions.assertEquals(fibonacciHeap.findMin().getKey(), 1); + Assertions.assertEquals(1, fibonacciHeap.findMin().getKey()); fibonacciHeap.deleteMin(); - Assertions.assertEquals(fibonacciHeap.findMin().getKey(), 3); + Assertions.assertEquals(3, fibonacciHeap.findMin().getKey()); + } + + @Test + void testDeleteMinOnSingleElementHeap() { + FibonacciHeap fibonacciHeap = new FibonacciHeap(10); + Assertions.assertEquals(10, fibonacciHeap.findMin().getKey()); + fibonacciHeap.deleteMin(); + Assertions.assertTrue(fibonacciHeap.empty()); + } + + @Test + void testHeapMeld() { + FibonacciHeap heap1 = new FibonacciHeap(); + FibonacciHeap heap2 = new FibonacciHeap(); + heap1.insert(1); + heap1.insert(2); + heap2.insert(3); + heap2.insert(4); + + heap1.meld(heap2); + Assertions.assertEquals(1, heap1.findMin().getKey()); + } + + @Test + void testHeapSize() { + FibonacciHeap fibonacciHeap = new FibonacciHeap(); + Assertions.assertEquals(0, fibonacciHeap.size()); + fibonacciHeap.insert(5); + Assertions.assertEquals(1, fibonacciHeap.size()); + fibonacciHeap.insert(3); + Assertions.assertEquals(2, fibonacciHeap.size()); + fibonacciHeap.deleteMin(); + Assertions.assertEquals(1, fibonacciHeap.size()); + } + + @Test + void testCountersRep() { + FibonacciHeap fibonacciHeap = new FibonacciHeap(); + fibonacciHeap.insert(5); + fibonacciHeap.insert(3); + fibonacciHeap.insert(8); + fibonacciHeap.insert(1); + + int[] counters = fibonacciHeap.countersRep(); + Assertions.assertEquals(4, counters[0]); + Assertions.assertEquals(0, counters[1]); + } + + @Test + void testDeleteMinMultipleElements() { + FibonacciHeap fibonacciHeap = new FibonacciHeap(); + fibonacciHeap.insert(5); + fibonacciHeap.insert(2); + fibonacciHeap.insert(8); + fibonacciHeap.insert(1); + + Assertions.assertEquals(1, fibonacciHeap.findMin().getKey()); + fibonacciHeap.deleteMin(); + Assertions.assertEquals(2, fibonacciHeap.findMin().getKey()); + } + + @Test + void testInsertNegativeKeys() { + FibonacciHeap fibonacciHeap = new FibonacciHeap(); + fibonacciHeap.insert(-10); + fibonacciHeap.insert(-5); + fibonacciHeap.insert(-20); + + Assertions.assertEquals(-20, fibonacciHeap.findMin().getKey()); + } + + @Test + void testDeleteOnEmptyHeap() { + FibonacciHeap fibonacciHeap = new FibonacciHeap(); + Assertions.assertThrows(NullPointerException.class, () -> { fibonacciHeap.delete(fibonacciHeap.findMin()); }); + } + + @Test + void testPotentialCalculation() { + FibonacciHeap fibonacciHeap = new FibonacciHeap(); + fibonacciHeap.insert(10); + fibonacciHeap.insert(20); + + Assertions.assertEquals(2, fibonacciHeap.potential()); // 2 trees, no marked nodes + var node = fibonacciHeap.findMin(); + fibonacciHeap.delete(node); + Assertions.assertEquals(1, fibonacciHeap.potential()); } } diff --git a/src/test/java/com/thealgorithms/datastructures/heaps/GenericHeapTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/GenericHeapTest.java new file mode 100644 index 000000000000..a3642996b769 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/heaps/GenericHeapTest.java @@ -0,0 +1,85 @@ +package com.thealgorithms.datastructures.heaps; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class GenericHeapTest { + + private GenericHeap heap; + + @BeforeEach + void setUp() { + heap = new GenericHeap<>(); + } + + @Test + void testAddAndGet() { + heap.add(10); + heap.add(20); + heap.add(5); + + assertEquals(20, heap.get()); + } + + @Test + void testRemove() { + heap.add(10); + heap.add(20); + heap.add(5); + + assertEquals(20, heap.remove()); + assertEquals(10, heap.get()); + } + + @Test + void testIsEmpty() { + assertTrue(heap.isEmpty()); + heap.add(1); + assertFalse(heap.isEmpty()); + } + + @Test + void testSize() { + assertEquals(0, heap.size()); + heap.add(1); + heap.add(2); + assertEquals(2, heap.size()); + } + + @Test + void testUpdatePriority() { + heap.add(10); + heap.add(20); + heap.add(5); + + heap.updatePriority(10); + assertEquals(20, heap.get()); + + heap.add(30); + heap.updatePriority(20); // 20 will be moved up + assertEquals(30, heap.get()); + } + + @Test + void testRemoveFromEmptyHeap() { + Exception exception = assertThrows(IllegalStateException.class, () -> heap.remove()); + assertEquals("Heap is empty", exception.getMessage()); + } + + @Test + void testGetFromEmptyHeap() { + Exception exception = assertThrows(IllegalStateException.class, () -> heap.get()); + assertEquals("Heap is empty", exception.getMessage()); + } + + @Test + void testUpdatePriorityForNonExistentItem() { + Exception exception = assertThrows(IllegalArgumentException.class, () -> heap.updatePriority(100)); + assertEquals("Item not found in the heap", exception.getMessage()); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/heaps/HeapElementTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/HeapElementTest.java new file mode 100644 index 000000000000..d04a9de8a94b --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/heaps/HeapElementTest.java @@ -0,0 +1,55 @@ +package com.thealgorithms.datastructures.heaps; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +class HeapElementTest { + + @Test + void testConstructorAndGetters() { + HeapElement element = new HeapElement(5.0, "Info"); + assertEquals(5.0, element.getKey()); + assertEquals("Info", element.getInfo()); + } + + @Test + void testConstructorWithNullInfo() { + HeapElement element = new HeapElement(10); + assertEquals(10, element.getKey()); + assertNull(element.getInfo()); + } + + @Test + void testToString() { + HeapElement element = new HeapElement(7.5, "TestInfo"); + assertEquals("Key: 7.5 - TestInfo", element.toString()); + + HeapElement elementWithoutInfo = new HeapElement(3); + assertEquals("Key: 3.0 - No additional info", elementWithoutInfo.toString()); + } + + @Test + void testEquals() { + HeapElement element1 = new HeapElement(2.5, "Data"); + HeapElement element2 = new HeapElement(2.5, "Data"); + HeapElement element3 = new HeapElement(3.0, "DifferentData"); + + assertEquals(element1, element2); // Same key and info + assertNotEquals(element1, element3); // Different key + assertNotEquals(null, element1); // Check for null + assertNotEquals("String", element1); // Check for different type + } + + @Test + void testHashCode() { + HeapElement element1 = new HeapElement(4, "HashMe"); + HeapElement element2 = new HeapElement(4, "HashMe"); + HeapElement element3 = new HeapElement(4, "DifferentHash"); + + assertEquals(element1.hashCode(), element2.hashCode()); // Same key and info + assertNotEquals(element1.hashCode(), element3.hashCode()); // Different info + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/heaps/KthElementFinderTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/KthElementFinderTest.java new file mode 100644 index 000000000000..7b92a57eaa77 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/heaps/KthElementFinderTest.java @@ -0,0 +1,19 @@ +package com.thealgorithms.datastructures.heaps; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class KthElementFinderTest { + @Test + public void testFindKthLargest() { + int[] nums = {3, 2, 1, 5, 6, 4}; + assertEquals(5, KthElementFinder.findKthLargest(nums, 2)); + } + + @Test + public void testFindKthSmallest() { + int[] nums = {7, 10, 4, 3, 20, 15}; + assertEquals(7, KthElementFinder.findKthSmallest(nums, 3)); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/heaps/LeftistHeapTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/LeftistHeapTest.java index f4c4c548ffbf..8c313237f88f 100644 --- a/src/test/java/com/thealgorithms/datastructures/heaps/LeftistHeapTest.java +++ b/src/test/java/com/thealgorithms/datastructures/heaps/LeftistHeapTest.java @@ -6,23 +6,80 @@ public class LeftistHeapTest { @Test - void testLeftistHeap() { + void testIsEmpty() { + LeftistHeap heap = new LeftistHeap(); + Assertions.assertTrue(heap.isEmpty(), "Heap should be empty initially."); + + heap.insert(10); + Assertions.assertFalse(heap.isEmpty(), "Heap should not be empty after insertion."); + + heap.clear(); + Assertions.assertTrue(heap.isEmpty(), "Heap should be empty after clearing."); + } + + @Test + void testInsertAndExtractMin() { LeftistHeap heap = new LeftistHeap(); - Assertions.assertTrue(heap.isEmpty()); heap.insert(6); - Assertions.assertTrue(!heap.isEmpty()); heap.insert(2); heap.insert(3); heap.insert(1); - heap.inOrder(); - Assertions.assertTrue(heap.inOrder().toString().equals("[6, 2, 3, 1]")); - Assertions.assertTrue(heap.extractMin() == 1); - Assertions.assertTrue(heap.inOrder().toString().equals("[6, 2, 3]")); + + Assertions.assertEquals(1, heap.extractMin(), "Minimum should be 1."); + Assertions.assertEquals(2, heap.extractMin(), "Next minimum should be 2."); + Assertions.assertEquals(3, heap.extractMin(), "Next minimum should be 3."); + Assertions.assertEquals(6, heap.extractMin(), "Next minimum should be 6."); + Assertions.assertEquals(-1, heap.extractMin(), "Extracting from an empty heap should return -1."); + } + + @Test + void testMerge() { + LeftistHeap heap1 = new LeftistHeap(); + heap1.insert(1); + heap1.insert(3); + heap1.insert(5); + + LeftistHeap heap2 = new LeftistHeap(); + heap2.insert(2); + heap2.insert(4); + heap2.insert(6); + + heap1.merge(heap2); + + Assertions.assertEquals(1, heap1.extractMin(), "After merging, minimum should be 1."); + Assertions.assertEquals(2, heap1.extractMin(), "Next minimum should be 2."); + Assertions.assertEquals(3, heap1.extractMin(), "Next minimum should be 3."); + Assertions.assertEquals(4, heap1.extractMin(), "Next minimum should be 4."); + Assertions.assertEquals(5, heap1.extractMin(), "Next minimum should be 5."); + Assertions.assertEquals(6, heap1.extractMin(), "Next minimum should be 6."); + Assertions.assertEquals(-1, heap1.extractMin(), "Extracting from an empty heap should return -1."); + } + + @Test + void testInOrderTraversal() { + LeftistHeap heap = new LeftistHeap(); + heap.insert(10); + heap.insert(5); + heap.insert(20); + heap.insert(15); + heap.insert(30); + + Assertions.assertEquals("[20, 15, 30, 5, 10]", heap.inOrder().toString(), "In-order traversal should match the expected output."); + } + + @Test + void testMultipleExtractions() { + LeftistHeap heap = new LeftistHeap(); + heap.insert(10); + heap.insert(5); + heap.insert(3); heap.insert(8); - heap.insert(12); - heap.insert(4); - Assertions.assertTrue(heap.inOrder().toString().equals("[8, 3, 12, 2, 6, 4]")); - heap.clear(); - Assertions.assertTrue(heap.isEmpty()); + + // Extract multiple elements + Assertions.assertEquals(3, heap.extractMin()); + Assertions.assertEquals(5, heap.extractMin()); + Assertions.assertEquals(8, heap.extractMin()); + Assertions.assertEquals(10, heap.extractMin()); + Assertions.assertEquals(-1, heap.extractMin(), "Extracting from an empty heap should return -1."); } } diff --git a/src/test/java/com/thealgorithms/datastructures/heaps/MaxHeapTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/MaxHeapTest.java new file mode 100644 index 000000000000..c1b7eb3fd4ae --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/heaps/MaxHeapTest.java @@ -0,0 +1,144 @@ +package com.thealgorithms.datastructures.heaps; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for MaxHeap implementation + */ +class MaxHeapTest { + + private MaxHeap heap; + + @BeforeEach + void setUp() { + // Create a fresh heap for each test + List elements = Arrays.asList(new HeapElement(5.0, "Five"), new HeapElement(2.0, "Two"), new HeapElement(8.0, "Eight"), new HeapElement(1.0, "One"), new HeapElement(9.0, "Nine")); + heap = new MaxHeap(elements); + } + + @Test + void testConstructorWithNullList() { + assertThrows(IllegalArgumentException.class, () -> new MaxHeap(null)); + } + + @Test + void testConstructorWithEmptyList() { + MaxHeap emptyHeap = new MaxHeap(new ArrayList<>()); + assertTrue(emptyHeap.isEmpty()); + } + + @Test + void testConstructorWithNullElements() { + List elements = Arrays.asList(new HeapElement(1.0, "One"), null, new HeapElement(2.0, "Two")); + MaxHeap heap = new MaxHeap(elements); + assertEquals(2, heap.size()); + } + + @Test + void testInsertElement() { + heap.insertElement(new HeapElement(10.0, "Ten")); + assertEquals(10.0, heap.getElement(1).getKey()); + assertEquals(6, heap.size()); + } + + @Test + void testInsertNullElement() { + assertThrows(IllegalArgumentException.class, () -> heap.insertElement(null)); + } + + @Test + void testGetElementAtIndex() { + HeapElement element = heap.getElement(1); + assertEquals(9.0, element.getKey()); + assertEquals("Nine", element.getValue()); + } + + @Test + void testGetElementAtInvalidIndex() { + assertThrows(IndexOutOfBoundsException.class, () -> heap.getElement(0)); + assertThrows(IndexOutOfBoundsException.class, () -> heap.getElement(10)); + } + + @Test + void testDeleteElement() throws EmptyHeapException { + heap.deleteElement(1); + assertEquals(8.0, heap.getElement(1).getKey()); + assertEquals(4, heap.size()); + } + + @Test + void testDeleteElementAtInvalidIndex() { + assertThrows(IndexOutOfBoundsException.class, () -> heap.deleteElement(0)); + assertThrows(IndexOutOfBoundsException.class, () -> heap.deleteElement(10)); + } + + @Test + void testDeleteFromEmptyHeap() { + MaxHeap emptyHeap = new MaxHeap(new ArrayList<>()); + assertThrows(EmptyHeapException.class, () -> emptyHeap.deleteElement(1)); + } + + @Test + void testExtractMax() throws EmptyHeapException { + HeapElement max = heap.getElement(); + assertEquals(9.0, max.getKey()); + assertEquals("Nine", max.getValue()); + assertEquals(4, heap.size()); + + max = heap.getElement(); + assertEquals(8.0, max.getKey()); + assertEquals(3, heap.size()); + } + + @Test + void testExtractMaxFromEmptyHeap() { + MaxHeap emptyHeap = new MaxHeap(new ArrayList<>()); + assertThrows(EmptyHeapException.class, () -> emptyHeap.getElement()); + } + + @Test + void testHeapOrder() { + // Test that parent is always greater than or equal to children + for (int i = 1; i <= heap.size() / 2; i++) { + double parentKey = heap.getElement(i).getKey(); + + // Check left child + if (2 * i <= heap.size()) { + assertTrue(parentKey >= heap.getElement(2 * i).getKey()); + } + + // Check right child + if (2 * i + 1 <= heap.size()) { + assertTrue(parentKey >= heap.getElement(2 * i + 1).getKey()); + } + } + } + + @Test + void testSizeAndEmpty() { + assertEquals(5, heap.size()); + assertFalse(heap.isEmpty()); + + // Remove all elements + while (!heap.isEmpty()) { + try { + heap.getElement(); + } catch (EmptyHeapException e) { + Assertions.fail("Should not throw EmptyHeapException while heap is not empty"); + } + } + + assertEquals(0, heap.size()); + assertTrue(heap.isEmpty()); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/heaps/MedianFinderTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/MedianFinderTest.java new file mode 100644 index 000000000000..fcacb21fcfc3 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/heaps/MedianFinderTest.java @@ -0,0 +1,17 @@ +package com.thealgorithms.datastructures.heaps; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class MedianFinderTest { + @Test + public void testMedianMaintenance() { + MedianFinder mf = new MedianFinder(); + mf.addNum(1); + mf.addNum(2); + assertEquals(1.5, mf.findMedian()); + mf.addNum(3); + assertEquals(2.0, mf.findMedian()); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/heaps/MergeKSortedArraysTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/MergeKSortedArraysTest.java new file mode 100644 index 000000000000..c9b754619286 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/heaps/MergeKSortedArraysTest.java @@ -0,0 +1,41 @@ +package com.thealgorithms.datastructures.heaps; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class MergeKSortedArraysTest { + + @ParameterizedTest + @MethodSource("provideTestCases") + public void testMergeKArrays(int[][] arrays, int[] expected) { + assertArrayEquals(expected, MergeKSortedArrays.mergeKArrays(arrays)); + } + + private static Stream provideTestCases() { + return Stream.of( + // Basic test case with multiple arrays + Arguments.of(new int[][] {{1, 4, 5}, {1, 3, 4}, {2, 6}}, new int[] {1, 1, 2, 3, 4, 4, 5, 6}), + + // Edge case: All arrays are empty + Arguments.of(new int[][] {{}, {}, {}}, new int[] {}), + + // Edge case: One array is empty + Arguments.of(new int[][] {{1, 3, 5}, {}, {2, 4, 6}}, new int[] {1, 2, 3, 4, 5, 6}), + + // Single array + Arguments.of(new int[][] {{1, 2, 3}}, new int[] {1, 2, 3}), + + // Arrays with negative numbers + Arguments.of(new int[][] {{-5, 1, 3}, {-10, 0, 2}}, new int[] {-10, -5, 0, 1, 2, 3}), + + // Arrays with duplicate elements + Arguments.of(new int[][] {{1, 1, 2}, {1, 3, 3}, {2, 2, 4}}, new int[] {1, 1, 1, 2, 2, 2, 3, 3, 4}), + + // Edge case: Arrays of varying lengths + Arguments.of(new int[][] {{1, 2}, {3}, {4, 5, 6, 7}}, new int[] {1, 2, 3, 4, 5, 6, 7})); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/heaps/MinHeapTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/MinHeapTest.java new file mode 100644 index 000000000000..1c2caf54cdb1 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/heaps/MinHeapTest.java @@ -0,0 +1,141 @@ +package com.thealgorithms.datastructures.heaps; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class MinHeapTest { + + private MinHeap heap; + + @BeforeEach + void setUp() { + // Create a fresh heap for each test + List elements = Arrays.asList(new HeapElement(5.0, "Five"), new HeapElement(2.0, "Two"), new HeapElement(8.0, "Eight"), new HeapElement(1.0, "One"), new HeapElement(9.0, "Nine")); + heap = new MinHeap(elements); + } + + @Test + void testConstructorWithNullList() { + assertThrows(IllegalArgumentException.class, () -> new MinHeap(null)); + } + + @Test + void testConstructorWithEmptyList() { + MinHeap emptyHeap = new MinHeap(new ArrayList<>()); + assertTrue(emptyHeap.isEmpty()); + } + + @Test + void testConstructorWithNullElements() { + List elements = Arrays.asList(new HeapElement(1.0, "One"), null, new HeapElement(2.0, "Two")); + MinHeap heap = new MinHeap(elements); + assertEquals(2, heap.size()); + } + + @Test + void testInsertElement() { + heap.insertElement(new HeapElement(0.5, "Half")); + assertEquals(0.5, heap.getElement(1).getKey()); + assertEquals(6, heap.size()); + } + + @Test + void testInsertNullElement() { + assertThrows(IllegalArgumentException.class, () -> heap.insertElement(null)); + } + + @Test + void testGetElementAtIndex() { + HeapElement element = heap.getElement(1); + assertEquals(1.0, element.getKey()); + assertEquals("One", element.getValue()); + } + + @Test + void testGetElementAtInvalidIndex() { + assertThrows(IndexOutOfBoundsException.class, () -> heap.getElement(0)); + assertThrows(IndexOutOfBoundsException.class, () -> heap.getElement(10)); + } + + @Test + void testDeleteElement() throws EmptyHeapException { + heap.deleteElement(1); + assertEquals(2.0, heap.getElement(1).getKey()); + assertEquals(4, heap.size()); + } + + @Test + void testDeleteElementAtInvalidIndex() { + assertThrows(IndexOutOfBoundsException.class, () -> heap.deleteElement(0)); + assertThrows(IndexOutOfBoundsException.class, () -> heap.deleteElement(10)); + } + + @Test + void testDeleteFromEmptyHeap() { + MinHeap emptyHeap = new MinHeap(new ArrayList<>()); + assertThrows(EmptyHeapException.class, () -> emptyHeap.deleteElement(1)); + } + + @Test + void testExtractMin() throws EmptyHeapException { + HeapElement min = heap.getElement(); + assertEquals(1.0, min.getKey()); + assertEquals("One", min.getValue()); + assertEquals(4, heap.size()); + + min = heap.getElement(); + assertEquals(2.0, min.getKey()); + assertEquals(3, heap.size()); + } + + @Test + void testExtractMinFromEmptyHeap() { + MinHeap emptyHeap = new MinHeap(new ArrayList<>()); + assertThrows(EmptyHeapException.class, () -> emptyHeap.getElement()); + } + + @Test + void testHeapOrder() { + // Test that parent is always smaller than or equal to children + for (int i = 1; i <= heap.size() / 2; i++) { + double parentKey = heap.getElement(i).getKey(); + + // Check left child + if (2 * i <= heap.size()) { + assertTrue(parentKey <= heap.getElement(2 * i).getKey()); + } + + // Check right child + if (2 * i + 1 <= heap.size()) { + assertTrue(parentKey <= heap.getElement(2 * i + 1).getKey()); + } + } + } + + @Test + void testSizeAndEmpty() { + assertEquals(5, heap.size()); + assertFalse(heap.isEmpty()); + + // Remove all elements + while (!heap.isEmpty()) { + try { + heap.getElement(); + } catch (EmptyHeapException e) { + Assertions.fail("Should not throw EmptyHeapException while heap is not empty"); + } + } + + assertEquals(0, heap.size()); + assertTrue(heap.isEmpty()); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/heaps/MinPriorityQueueTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/MinPriorityQueueTest.java new file mode 100644 index 000000000000..8f93bd630aa7 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/heaps/MinPriorityQueueTest.java @@ -0,0 +1,87 @@ +package com.thealgorithms.datastructures.heaps; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class MinPriorityQueueTest { + + @Test + void testInsertAndPeek() { + MinPriorityQueue queue = new MinPriorityQueue(5); + queue.insert(10); + queue.insert(5); + queue.insert(15); + + Assertions.assertEquals(5, queue.peek(), "The minimum element should be 5."); + } + + @Test + void testDelete() { + MinPriorityQueue queue = new MinPriorityQueue(5); + queue.insert(10); + queue.insert(5); + queue.insert(15); + + Assertions.assertEquals(5, queue.delete(), "The deleted minimum element should be 5."); + Assertions.assertEquals(10, queue.peek(), "After deletion, the new minimum should be 10."); + } + + @Test + void testIsEmpty() { + MinPriorityQueue queue = new MinPriorityQueue(5); + Assertions.assertTrue(queue.isEmpty(), "The queue should be empty initially."); + + queue.insert(10); + Assertions.assertFalse(queue.isEmpty(), "The queue should not be empty after insertion."); + } + + @Test + void testIsFull() { + MinPriorityQueue queue = new MinPriorityQueue(2); + queue.insert(10); + queue.insert(5); + + Assertions.assertTrue(queue.isFull(), "The queue should be full after inserting two elements."); + queue.delete(); + Assertions.assertFalse(queue.isFull(), "The queue should not be full after deletion."); + } + + @Test + void testHeapSort() { + MinPriorityQueue queue = new MinPriorityQueue(5); + queue.insert(10); + queue.insert(5); + queue.insert(15); + queue.insert(1); + queue.insert(3); + + // Delete all elements to sort the queue + int[] sortedArray = new int[5]; + for (int i = 0; i < 5; i++) { + sortedArray[i] = queue.delete(); + } + + Assertions.assertArrayEquals(new int[] {1, 3, 5, 10, 15}, sortedArray, "The array should be sorted in ascending order."); + } + + @Test + void testPeekEmptyQueue() { + MinPriorityQueue queue = new MinPriorityQueue(5); + Assertions.assertThrows(IllegalStateException.class, queue::peek, "Should throw an exception when peeking into an empty queue."); + } + + @Test + void testDeleteEmptyQueue() { + MinPriorityQueue queue = new MinPriorityQueue(5); + Assertions.assertThrows(IllegalStateException.class, queue::delete, "Should throw an exception when deleting from an empty queue."); + } + + @Test + void testInsertWhenFull() { + MinPriorityQueue queue = new MinPriorityQueue(2); + queue.insert(10); + queue.insert(5); + + Assertions.assertThrows(IllegalStateException.class, () -> queue.insert(15), "Should throw an exception when inserting into a full queue."); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/lists/CircleLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/CircleLinkedListTest.java index b7a05ef29d66..883d2b02ba7c 100644 --- a/src/test/java/com/thealgorithms/datastructures/lists/CircleLinkedListTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/CircleLinkedListTest.java @@ -1,78 +1,115 @@ package com.thealgorithms.datastructures.lists; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; public class CircleLinkedListTest { + private CircleLinkedList list; + + @BeforeEach + public void setUp() { + list = new CircleLinkedList<>(); + } + + @Test + public void testInitialSize() { + assertEquals(0, list.getSize(), "Initial size should be 0."); + } + @Test public void testAppendAndSize() { - CircleLinkedList list = new CircleLinkedList<>(); list.append(1); list.append(2); list.append(3); - assertEquals(3, list.getSize()); - assertEquals("[ 1, 2, 3 ]", list.toString()); + assertEquals(3, list.getSize(), "Size after three appends should be 3."); + assertEquals("[ 1, 2, 3 ]", list.toString(), "List content should match appended values."); } @Test public void testRemove() { - CircleLinkedList list = new CircleLinkedList<>(); list.append(1); list.append(2); list.append(3); list.append(4); - assertEquals(2, list.remove(1)); - assertEquals(3, list.remove(1)); - assertEquals("[ 1, 4 ]", list.toString()); - assertEquals(2, list.getSize()); + assertEquals(2, list.remove(1), "Removed element at index 1 should be 2."); + assertEquals(3, list.remove(1), "Removed element at index 1 after update should be 3."); + assertEquals("[ 1, 4 ]", list.toString(), "List content should reflect removals."); + assertEquals(2, list.getSize(), "Size after two removals should be 2."); } @Test public void testRemoveInvalidIndex() { - CircleLinkedList list = new CircleLinkedList<>(); list.append(1); list.append(2); - assertThrows(IndexOutOfBoundsException.class, () -> list.remove(2)); - assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1)); + assertThrows(IndexOutOfBoundsException.class, () -> list.remove(2), "Should throw on out-of-bounds index."); + assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1), "Should throw on negative index."); } @Test public void testToStringEmpty() { - CircleLinkedList list = new CircleLinkedList<>(); - assertEquals("[]", list.toString()); + assertEquals("[]", list.toString(), "Empty list should be represented by '[]'."); } @Test public void testToStringAfterRemoval() { - CircleLinkedList list = new CircleLinkedList<>(); list.append(1); list.append(2); list.append(3); list.remove(1); - assertEquals("[ 1, 3 ]", list.toString()); + assertEquals("[ 1, 3 ]", list.toString(), "List content should match remaining elements after removal."); } @Test public void testSingleElement() { - CircleLinkedList list = new CircleLinkedList<>(); list.append(1); - assertEquals(1, list.getSize()); - assertEquals("[ 1 ]", list.toString()); - assertEquals(1, list.remove(0)); - assertEquals("[]", list.toString()); + assertEquals(1, list.getSize(), "Size after single append should be 1."); + assertEquals("[ 1 ]", list.toString(), "Single element list should display properly."); + assertEquals(1, list.remove(0), "Single element removed should match appended value."); + assertEquals("[]", list.toString(), "List should be empty after removing the single element."); } @Test public void testNullElement() { - CircleLinkedList list = new CircleLinkedList<>(); - assertThrows(NullPointerException.class, () -> list.append(null)); + assertThrows(NullPointerException.class, () -> list.append(null), "Appending null should throw exception."); + } + + @Test + public void testCircularReference() { + list.append(1); + list.append(2); + list.append(3); + CircleLinkedList.Node current = list.head; + + // Traverse one full cycle and verify the circular reference + for (int i = 0; i <= list.getSize(); i++) { + current = current.next; + } + assertEquals(list.head, current, "End of list should point back to the head (circular structure)."); + } + + @Test + public void testClear() { + list.append(1); + list.append(2); + list.append(3); + + // Remove all elements to simulate clearing the list + for (int i = list.getSize() - 1; i >= 0; i--) { + list.remove(i); + } + + assertEquals(0, list.getSize(), "Size after clearing should be 0."); + assertEquals("[]", list.toString(), "Empty list should be represented by '[]' after clear."); + assertSame(list.head.next, list.head, "Head's next should point to itself after clearing."); } } diff --git a/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java b/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java new file mode 100644 index 000000000000..1a3efe8a5572 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java @@ -0,0 +1,49 @@ +package com.thealgorithms.datastructures.lists; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class CountSinglyLinkedListRecursionTest { + + private CountSinglyLinkedListRecursion list; + + @BeforeEach + public void setUp() { + list = new CountSinglyLinkedListRecursion(); + } + + @Test + public void testCountEmptyList() { + // An empty list should have a count of 0 + assertEquals(0, list.count(), "Count of an empty list should be 0."); + } + + @Test + public void testCountSingleElementList() { + // Insert a single element and check the count + list.insert(1); + assertEquals(1, list.count(), "Count of a single-element list should be 1."); + } + + @Test + public void testCountMultipleElements() { + // Insert multiple elements and check the count + for (int i = 1; i <= 5; i++) { + list.insert(i); + } + assertEquals(5, list.count(), "Count of a list with 5 elements should be 5."); + } + + @Test + public void testCountWithDuplicateElements() { + // Insert duplicate elements and verify the count is correct + list.insert(1); + list.insert(2); + list.insert(2); + list.insert(3); + list.insert(3); + assertEquals(5, list.count(), "Count of a list with duplicate elements should match total node count."); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoopTest.java b/src/test/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoopTest.java new file mode 100644 index 000000000000..2e1012f2b79b --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoopTest.java @@ -0,0 +1,86 @@ +package com.thealgorithms.datastructures.lists; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class CreateAndDetectLoopTest { + + private CreateAndDetectLoop.Node head; + + @BeforeEach + void setUp() { + // Set up a linked list: 1 -> 2 -> 3 -> 4 -> 5 -> 6 + head = new CreateAndDetectLoop.Node(1); + CreateAndDetectLoop.Node second = new CreateAndDetectLoop.Node(2); + CreateAndDetectLoop.Node third = new CreateAndDetectLoop.Node(3); + CreateAndDetectLoop.Node fourth = new CreateAndDetectLoop.Node(4); + CreateAndDetectLoop.Node fifth = new CreateAndDetectLoop.Node(5); + CreateAndDetectLoop.Node sixth = new CreateAndDetectLoop.Node(6); + + head.next = second; + second.next = third; + third.next = fourth; + fourth.next = fifth; + fifth.next = sixth; + } + + @Test + void testDetectLoopNoLoop() { + // Test when no loop exists + assertFalse(CreateAndDetectLoop.detectLoop(head), "There should be no loop."); + } + + @Test + void testCreateAndDetectLoopLoopExists() { + // Create a loop between position 2 (node with value 2) and position 5 (node with value 5) + CreateAndDetectLoop.createLoop(head, 2, 5); + + // Now test if the loop is detected + assertTrue(CreateAndDetectLoop.detectLoop(head), "A loop should be detected."); + } + + @Test + void testCreateLoopInvalidPosition() { + // Create loop with invalid positions (0) + CreateAndDetectLoop.createLoop(head, 0, 0); + + // Ensure no loop was created + assertFalse(CreateAndDetectLoop.detectLoop(head), "There should be no loop with invalid positions."); + } + + @Test + void testCreateLoopSelfLoop() { + // Create a self-loop at position 3 (node with value 3) + CreateAndDetectLoop.createLoop(head, 3, 3); + + // Test if the self-loop is detected + assertTrue(CreateAndDetectLoop.detectLoop(head), "A self-loop should be detected."); + } + + @Test + void testCreateLoopNoChangeForNonExistentPositions() { + // Create a loop with non-existent positions + CreateAndDetectLoop.createLoop(head, 10, 20); + + // Ensure no loop was created + assertFalse(CreateAndDetectLoop.detectLoop(head), "No loop should be created if positions are out of bounds."); + } + + @Test + void testMultipleNodesWithNoLoop() { + // Multiple nodes without creating any loop + assertFalse(CreateAndDetectLoop.detectLoop(head), "No loop should be detected for a standard linear list."); + } + + @Test + void testHeadToTailLoop() { + // Create a loop from the tail back to the head + CreateAndDetectLoop.createLoop(head, 1, 6); + + // Detect the head-to-tail loop + assertTrue(CreateAndDetectLoop.detectLoop(head), "A head-to-tail loop should be detected."); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/lists/CursorLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/CursorLinkedListTest.java new file mode 100644 index 000000000000..bf5501826994 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/lists/CursorLinkedListTest.java @@ -0,0 +1,112 @@ +package com.thealgorithms.datastructures.lists; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class CursorLinkedListTest { + private CursorLinkedList list; + + @BeforeEach + void setUp() { + list = new CursorLinkedList<>(); + } + + @Test + void testAppendAndGet() { + list.append("First"); + list.append("Second"); + list.append("Third"); + + assertEquals("First", list.get(0)); + assertEquals("Second", list.get(1)); + assertEquals("Third", list.get(2)); + assertNull(list.get(3)); + assertNull(list.get(-1)); + } + + @Test + void testIndexOf() { + list.append("First"); + list.append("Second"); + list.append("Third"); + + assertEquals(0, list.indexOf("First")); + assertEquals(1, list.indexOf("Second")); + assertEquals(2, list.indexOf("Third")); + assertEquals(-1, list.indexOf("NonExistent")); + } + + @Test + void testRemove() { + list.append("First"); + list.append("Second"); + list.append("Third"); + + list.remove("Second"); + assertEquals("First", list.get(0)); + assertEquals("Third", list.get(1)); + assertNull(list.get(2)); + assertEquals(-1, list.indexOf("Second")); + } + + @Test + void testRemoveByIndex() { + list.append("First"); + list.append("Second"); + list.append("Third"); + + list.removeByIndex(1); + assertEquals("First", list.get(0)); + assertEquals("Third", list.get(1)); + assertNull(list.get(2)); + } + + @Test + void testRemoveFirstElement() { + list.append("First"); + list.append("Second"); + + list.remove("First"); + assertEquals("Second", list.get(0)); + assertNull(list.get(1)); + assertEquals(-1, list.indexOf("First")); + } + + @Test + void testRemoveLastElement() { + list.append("First"); + list.append("Second"); + + list.remove("Second"); + assertEquals("First", list.get(0)); + assertNull(list.get(1)); + assertEquals(-1, list.indexOf("Second")); + } + + @Test + void testNullHandling() { + assertThrows(NullPointerException.class, () -> list.append(null)); + assertThrows(NullPointerException.class, () -> list.remove(null)); + assertThrows(NullPointerException.class, () -> list.indexOf(null)); + } + + @Test + void testEmptyList() { + assertNull(list.get(0)); + assertEquals(-1, list.indexOf("Any")); + } + + @Test + void testMemoryLimitExceeded() { + // Test adding more elements than CURSOR_SPACE_SIZE + assertThrows(OutOfMemoryError.class, () -> { + for (int i = 0; i < 101; i++) { // CURSOR_SPACE_SIZE is 100 + list.append("Element" + i); + } + }); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedListTest.java new file mode 100644 index 000000000000..99a890112d31 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedListTest.java @@ -0,0 +1,93 @@ +package com.thealgorithms.datastructures.lists; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import com.thealgorithms.datastructures.lists.MergeKSortedLinkedList.Node; +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +class MergeKSortedLinkedListTest { + + @Test + void testMergeKLists() { + Node list1 = new Node(1, new Node(4, new Node(5))); + Node list2 = new Node(1, new Node(3, new Node(4))); + Node list3 = new Node(2, new Node(6)); + Node[] lists = {list1, list2, list3}; + + MergeKSortedLinkedList merger = new MergeKSortedLinkedList(); + Node mergedHead = merger.mergeKList(lists, lists.length); + + int[] expectedValues = {1, 1, 2, 3, 4, 4, 5, 6}; + int[] actualValues = getListValues(mergedHead); + assertArrayEquals(expectedValues, actualValues, "Merged list values do not match the expected sorted order."); + } + + @Test + void testMergeEmptyLists() { + Node[] lists = {null, null, null}; + + MergeKSortedLinkedList merger = new MergeKSortedLinkedList(); + Node mergedHead = merger.mergeKList(lists, lists.length); + + assertNull(mergedHead, "Merged list should be null when all input lists are empty."); + } + + @Test + void testMergeSingleList() { + Node list1 = new Node(1, new Node(3, new Node(5))); + Node[] lists = {list1}; + + MergeKSortedLinkedList merger = new MergeKSortedLinkedList(); + Node mergedHead = merger.mergeKList(lists, lists.length); + + int[] expectedValues = {1, 3, 5}; + int[] actualValues = getListValues(mergedHead); + assertArrayEquals(expectedValues, actualValues, "Merged list should match the single input list when only one list is provided."); + } + + @Test + void testMergeListsOfDifferentLengths() { + Node list1 = new Node(1, new Node(3, new Node(5))); + Node list2 = new Node(2, new Node(4)); + Node list3 = new Node(6); + Node[] lists = {list1, list2, list3}; + + MergeKSortedLinkedList merger = new MergeKSortedLinkedList(); + Node mergedHead = merger.mergeKList(lists, lists.length); + + int[] expectedValues = {1, 2, 3, 4, 5, 6}; + int[] actualValues = getListValues(mergedHead); + assertArrayEquals(expectedValues, actualValues, "Merged list values do not match expected sorted order for lists of different lengths."); + } + + @Test + void testMergeSingleElementLists() { + Node list1 = new Node(1); + Node list2 = new Node(3); + Node list3 = new Node(2); + Node[] lists = {list1, list2, list3}; + + MergeKSortedLinkedList merger = new MergeKSortedLinkedList(); + Node mergedHead = merger.mergeKList(lists, lists.length); + + int[] expectedValues = {1, 2, 3}; + int[] actualValues = getListValues(mergedHead); + assertArrayEquals(expectedValues, actualValues, "Merged list values do not match expected sorted order for single-element lists."); + } + + /** + * Helper method to extract values from the linked list into an array for assertion. + */ + private int[] getListValues(Node head) { + int[] values = new int[100]; // assuming max length for simplicity + int i = 0; + Node curr = head; + while (curr != null) { + values[i++] = curr.data; + curr = curr.next; + } + return Arrays.copyOf(values, i); // return only filled part + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java new file mode 100644 index 000000000000..5483bbcd0394 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java @@ -0,0 +1,99 @@ +package com.thealgorithms.datastructures.lists; + +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 org.junit.jupiter.api.Test; + +class MergeSortedArrayListTest { + + @Test + void testMergeTwoSortedLists() { + List listA = Arrays.asList(1, 3, 5, 7, 9); + List listB = Arrays.asList(2, 4, 6, 8, 10); + 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."); + } + + @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."); + } + + @Test + void testMergeWithDuplicateElements() { + List listA = Arrays.asList(1, 2, 2, 3); + List listB = Arrays.asList(2, 3, 4); + 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."); + } + + @Test + void testMergeWithNegativeAndPositiveNumbers() { + List listA = Arrays.asList(-3, -1, 2); + List listB = Arrays.asList(-2, 0, 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."); + } + + @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."); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedListTest.java new file mode 100644 index 000000000000..b6b785f43711 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedListTest.java @@ -0,0 +1,90 @@ +package com.thealgorithms.datastructures.lists; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class MergeSortedSinglyLinkedListTest { + + @Test + void testMergeTwoSortedLists() { + SinglyLinkedList listA = new SinglyLinkedList(); + SinglyLinkedList listB = new SinglyLinkedList(); + + for (int i = 2; i <= 10; i += 2) { + listA.insert(i); + listB.insert(i - 1); + } + + SinglyLinkedList mergedList = MergeSortedSinglyLinkedList.merge(listA, listB); + assertEquals("1->2->3->4->5->6->7->8->9->10", mergedList.toString(), "Merged list should contain all elements in sorted order."); + } + + @Test + void testMergeWithEmptyListA() { + SinglyLinkedList listA = new SinglyLinkedList(); // Empty listA + SinglyLinkedList listB = new SinglyLinkedList(); + listB.insert(1); + listB.insert(3); + listB.insert(5); + + SinglyLinkedList mergedList = MergeSortedSinglyLinkedList.merge(listA, listB); + assertEquals("1->3->5", mergedList.toString(), "Merged list should match listB when listA is empty."); + } + + @Test + void testMergeWithEmptyListB() { + SinglyLinkedList listA = new SinglyLinkedList(); + SinglyLinkedList listB = new SinglyLinkedList(); // Empty listB + listA.insert(2); + listA.insert(4); + listA.insert(6); + + SinglyLinkedList mergedList = MergeSortedSinglyLinkedList.merge(listA, listB); + assertEquals("2->4->6", mergedList.toString(), "Merged list should match listA when listB is empty."); + } + + @Test + void testMergeWithBothEmptyLists() { + SinglyLinkedList listA = new SinglyLinkedList(); // Empty listA + SinglyLinkedList listB = new SinglyLinkedList(); // Empty listB + + SinglyLinkedList mergedList = MergeSortedSinglyLinkedList.merge(listA, listB); + assertEquals("", mergedList.toString(), "Merged list should be empty when both input lists are empty."); + } + + @Test + void testMergeWithDuplicateValues() { + SinglyLinkedList listA = new SinglyLinkedList(); + SinglyLinkedList listB = new SinglyLinkedList(); + + listA.insert(1); + listA.insert(3); + listA.insert(5); + listB.insert(1); + listB.insert(4); + listB.insert(5); + + SinglyLinkedList mergedList = MergeSortedSinglyLinkedList.merge(listA, listB); + assertEquals("1->1->3->4->5->5", mergedList.toString(), "Merged list should include duplicate values in sorted order."); + } + + @Test + void testMergeThrowsExceptionOnNullInput() { + SinglyLinkedList listA = null; + SinglyLinkedList listB = new SinglyLinkedList(); + listB.insert(1); + listB.insert(2); + + SinglyLinkedList finalListA = listA; + SinglyLinkedList finalListB = listB; + assertThrows(NullPointerException.class, () -> MergeSortedSinglyLinkedList.merge(finalListA, finalListB), "Should throw NullPointerException if listA is null."); + + listA = new SinglyLinkedList(); + listB = null; + SinglyLinkedList finalListA1 = listA; + SinglyLinkedList finalListB1 = listB; + assertThrows(NullPointerException.class, () -> MergeSortedSinglyLinkedList.merge(finalListA1, finalListB1), "Should throw NullPointerException if listB is null."); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/lists/QuickSortLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/QuickSortLinkedListTest.java index 91e2ba5d43ce..4e86f317c2e8 100644 --- a/src/test/java/com/thealgorithms/datastructures/lists/QuickSortLinkedListTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/QuickSortLinkedListTest.java @@ -4,8 +4,9 @@ import static org.junit.jupiter.api.Assertions.assertNull; import org.junit.jupiter.api.Test; + /** - * Test cases for QuickSortLinkedList + * Test cases for QuickSortLinkedList. * Author: Prabhat-Kumar-42 * GitHub: https://github.com/Prabhat-Kumar-42 */ @@ -16,9 +17,8 @@ public void testSortEmptyList() { SinglyLinkedList emptyList = new SinglyLinkedList(); QuickSortLinkedList sorter = new QuickSortLinkedList(emptyList); - // Test case: Sorting an empty list should result in an empty list sorter.sortList(); - assertNull(emptyList.getHead()); + assertNull(emptyList.getHead(), "Sorted empty list should have no elements."); } @Test @@ -27,10 +27,51 @@ public void testSortSingleNodeList() { singleNodeList.insert(5); QuickSortLinkedList sorter = new QuickSortLinkedList(singleNodeList); - // Test case: Sorting a list with a single node should result in the same list sorter.sortList(); - assertEquals(5, singleNodeList.getHead().value); - assertNull(singleNodeList.getHead().next); + assertEquals(5, singleNodeList.getHead().value, "Single node list should remain unchanged after sorting."); + assertNull(singleNodeList.getHead().next, "Single node should not have a next node."); + } + + @Test + public void testSortAlreadySorted() { + SinglyLinkedList sortedList = new SinglyLinkedList(); + sortedList.insert(1); + sortedList.insert(2); + sortedList.insert(3); + sortedList.insert(4); + sortedList.insert(5); + QuickSortLinkedList sorter = new QuickSortLinkedList(sortedList); + + sorter.sortList(); + assertEquals("1->2->3->4->5", sortedList.toString(), "Already sorted list should remain unchanged."); + } + + @Test + public void testSortReverseOrderedList() { + SinglyLinkedList reverseList = new SinglyLinkedList(); + reverseList.insert(5); + reverseList.insert(4); + reverseList.insert(3); + reverseList.insert(2); + reverseList.insert(1); + QuickSortLinkedList sorter = new QuickSortLinkedList(reverseList); + + sorter.sortList(); + assertEquals("1->2->3->4->5", reverseList.toString(), "Reverse ordered list should be sorted in ascending order."); + } + + @Test + public void testSortWithDuplicates() { + SinglyLinkedList listWithDuplicates = new SinglyLinkedList(); + listWithDuplicates.insert(3); + listWithDuplicates.insert(1); + listWithDuplicates.insert(3); + listWithDuplicates.insert(2); + listWithDuplicates.insert(2); + QuickSortLinkedList sorter = new QuickSortLinkedList(listWithDuplicates); + + sorter.sortList(); + assertEquals("1->2->2->3->3", listWithDuplicates.toString(), "List with duplicates should be sorted correctly."); } @Test @@ -48,18 +89,7 @@ public void testSortMultipleElementsList() { list.insert(6); QuickSortLinkedList sorter = new QuickSortLinkedList(list); - // Test case: Sorting a list with multiple elements sorter.sortList(); - assertEquals(1, list.getHead().value); - assertEquals(2, list.getHead().next.value); - assertEquals(3, list.getHead().next.next.value); - assertEquals(4, list.getHead().next.next.next.value); - assertEquals(5, list.getHead().next.next.next.next.value); - assertEquals(6, list.getHead().next.next.next.next.next.value); - assertEquals(7, list.getHead().next.next.next.next.next.next.value); - assertEquals(8, list.getHead().next.next.next.next.next.next.next.value); - assertEquals(9, list.getHead().next.next.next.next.next.next.next.next.value); - assertEquals(10, list.getHead().next.next.next.next.next.next.next.next.next.value); - assertNull(list.getHead().next.next.next.next.next.next.next.next.next.next); + assertEquals("1->2->3->4->5->6->7->8->9->10", list.toString(), "List should be sorted in ascending order."); } } diff --git a/src/test/java/com/thealgorithms/datastructures/lists/ReverseKGroupTest.java b/src/test/java/com/thealgorithms/datastructures/lists/ReverseKGroupTest.java index e7e3cca4083f..76b7ab063de4 100644 --- a/src/test/java/com/thealgorithms/datastructures/lists/ReverseKGroupTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/ReverseKGroupTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import org.junit.jupiter.api.Test; + /** * Test cases for Reverse K Group LinkedList * Author: Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi) @@ -19,8 +20,8 @@ public void testReverseKGroupWithEmptyList() { @Test public void testReverseKGroupWithSingleNodeList() { ReverseKGroup reverser = new ReverseKGroup(); - Node singleNode = new Node(5); - Node result = reverser.reverseKGroup(singleNode, 2); + SinglyLinkedListNode singleNode = new SinglyLinkedListNode(5); + SinglyLinkedListNode result = reverser.reverseKGroup(singleNode, 2); assertEquals(5, result.value); assertNull(result.next); } @@ -30,15 +31,15 @@ public void testReverseKGroupWithKEqualTo2() { ReverseKGroup reverser = new ReverseKGroup(); // Create a list with multiple elements (1 -> 2 -> 3 -> 4 -> 5) - Node head; - head = new Node(1); - head.next = new Node(2); - head.next.next = new Node(3); - head.next.next.next = new Node(4); - head.next.next.next.next = new Node(5); + SinglyLinkedListNode head; + head = new SinglyLinkedListNode(1); + head.next = new SinglyLinkedListNode(2); + head.next.next = new SinglyLinkedListNode(3); + head.next.next.next = new SinglyLinkedListNode(4); + head.next.next.next.next = new SinglyLinkedListNode(5); // Test reverse with k=2 - Node result1 = reverser.reverseKGroup(head, 2); + SinglyLinkedListNode result1 = reverser.reverseKGroup(head, 2); assertEquals(2, result1.value); assertEquals(1, result1.next.value); assertEquals(4, result1.next.next.value); @@ -52,15 +53,15 @@ public void testReverseKGroupWithKEqualTo3() { ReverseKGroup reverser = new ReverseKGroup(); // Create a list with multiple elements (1 -> 2 -> 3 -> 4 -> 5) - Node head; - head = new Node(1); - head.next = new Node(2); - head.next.next = new Node(3); - head.next.next.next = new Node(4); - head.next.next.next.next = new Node(5); + SinglyLinkedListNode head; + head = new SinglyLinkedListNode(1); + head.next = new SinglyLinkedListNode(2); + head.next.next = new SinglyLinkedListNode(3); + head.next.next.next = new SinglyLinkedListNode(4); + head.next.next.next.next = new SinglyLinkedListNode(5); // Test reverse with k=3 - Node result = reverser.reverseKGroup(head, 3); + SinglyLinkedListNode result = reverser.reverseKGroup(head, 3); assertEquals(3, result.value); assertEquals(2, result.next.value); assertEquals(1, result.next.next.value); diff --git a/src/test/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedListsTest.java b/src/test/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedListsTest.java index 8b2ae424364e..c476ad1b0203 100644 --- a/src/test/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedListsTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedListsTest.java @@ -6,67 +6,100 @@ import org.junit.jupiter.api.Test; /** - * Test cases for RotateSinglyLinkedLists + * Test cases for RotateSinglyLinkedLists. * Author: Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi) */ public class RotateSinglyLinkedListsTest { + private final RotateSinglyLinkedLists rotator = new RotateSinglyLinkedLists(); + + // Helper method to create a linked list from an array of values + private SinglyLinkedListNode createLinkedList(int[] values) { + if (values.length == 0) { + return null; + } + + SinglyLinkedListNode head = new SinglyLinkedListNode(values[0]); + SinglyLinkedListNode current = head; + for (int i = 1; i < values.length; i++) { + current.next = new SinglyLinkedListNode(values[i]); + current = current.next; + } + return head; + } + + // Helper method to convert a linked list to a string for easy comparison + private String linkedListToString(SinglyLinkedListNode head) { + StringBuilder sb = new StringBuilder(); + SinglyLinkedListNode current = head; + while (current != null) { + sb.append(current.value); + if (current.next != null) { + sb.append(" -> "); + } + current = current.next; + } + return sb.toString(); + } + @Test public void testRotateRightEmptyList() { - RotateSinglyLinkedLists rotator = new RotateSinglyLinkedLists(); - - // Test case: Rotate an empty list + // Rotate an empty list assertNull(rotator.rotateRight(null, 2)); } @Test public void testRotateRightSingleNodeList() { - RotateSinglyLinkedLists rotator = new RotateSinglyLinkedLists(); - - // Test case: Rotate a list with one element - Node singleNode = new Node(5); - Node rotatedSingleNode = rotator.rotateRight(singleNode, 3); - assertEquals(5, rotatedSingleNode.value); - assertNull(rotatedSingleNode.next); + // Rotate a list with a single element + SinglyLinkedListNode singleNode = new SinglyLinkedListNode(5); + SinglyLinkedListNode rotatedSingleNode = rotator.rotateRight(singleNode, 3); + assertEquals("5", linkedListToString(rotatedSingleNode)); } @Test public void testRotateRightMultipleElementsList() { - RotateSinglyLinkedLists rotator = new RotateSinglyLinkedLists(); + // Rotate a list with multiple elements (rotate by 2) + SinglyLinkedListNode head = createLinkedList(new int[] {1, 2, 3, 4, 5}); + SinglyLinkedListNode rotated = rotator.rotateRight(head, 2); + assertEquals("4 -> 5 -> 1 -> 2 -> 3", linkedListToString(rotated)); + } - // Test case: Rotate a list with multiple elements (Rotate by 2) - Node head = new Node(1); - head.next = new Node(2); - head.next.next = new Node(3); - head.next.next.next = new Node(4); - head.next.next.next.next = new Node(5); + @Test + public void testRotateRightFullRotation() { + // Rotate by more than the length of the list + SinglyLinkedListNode head = createLinkedList(new int[] {1, 2, 3, 4, 5}); + SinglyLinkedListNode rotated = rotator.rotateRight(head, 7); + assertEquals("4 -> 5 -> 1 -> 2 -> 3", linkedListToString(rotated)); + } - Node rotated1 = rotator.rotateRight(head, 2); - assertEquals(4, rotated1.value); - assertEquals(5, rotated1.next.value); - assertEquals(1, rotated1.next.next.value); - assertEquals(2, rotated1.next.next.next.value); - assertEquals(3, rotated1.next.next.next.next.value); - assertNull(rotated1.next.next.next.next.next); + @Test + public void testRotateRightZeroRotation() { + // Rotate a list by k = 0 (no rotation) + SinglyLinkedListNode head = createLinkedList(new int[] {1, 2, 3, 4, 5}); + SinglyLinkedListNode rotated = rotator.rotateRight(head, 0); + assertEquals("1 -> 2 -> 3 -> 4 -> 5", linkedListToString(rotated)); } @Test - public void testRotateRightFullRotation() { - RotateSinglyLinkedLists rotator = new RotateSinglyLinkedLists(); + public void testRotateRightByListLength() { + // Rotate a list by k equal to list length (no change) + SinglyLinkedListNode head = createLinkedList(new int[] {1, 2, 3, 4, 5}); + SinglyLinkedListNode rotated = rotator.rotateRight(head, 5); + assertEquals("1 -> 2 -> 3 -> 4 -> 5", linkedListToString(rotated)); + } - // Test case: Rotate a list with multiple elements (Full rotation) - Node head = new Node(1); - head.next = new Node(2); - head.next.next = new Node(3); - head.next.next.next = new Node(4); - head.next.next.next.next = new Node(5); + @Test + public void testRotateRightByMultipleOfListLength() { + SinglyLinkedListNode head = createLinkedList(new int[] {1, 2, 3, 4, 5}); + SinglyLinkedListNode rotated = rotator.rotateRight(head, 10); // k = 2 * list length + assertEquals("1 -> 2 -> 3 -> 4 -> 5", linkedListToString(rotated)); + } - Node rotated3 = rotator.rotateRight(head, 7); - assertEquals(4, rotated3.value); - assertEquals(5, rotated3.next.value); - assertEquals(1, rotated3.next.next.value); - assertEquals(2, rotated3.next.next.next.value); - assertEquals(3, rotated3.next.next.next.next.value); - assertNull(rotated3.next.next.next.next.next); + @Test + public void testRotateRightLongerList() { + // Rotate a longer list by a smaller k + SinglyLinkedListNode head = createLinkedList(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9}); + SinglyLinkedListNode rotated = rotator.rotateRight(head, 4); + assertEquals("6 -> 7 -> 8 -> 9 -> 1 -> 2 -> 3 -> 4 -> 5", linkedListToString(rotated)); } } diff --git a/src/test/java/com/thealgorithms/datastructures/lists/SearchSinglyLinkedListRecursionTest.java b/src/test/java/com/thealgorithms/datastructures/lists/SearchSinglyLinkedListRecursionTest.java new file mode 100644 index 000000000000..76b905841c18 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/lists/SearchSinglyLinkedListRecursionTest.java @@ -0,0 +1,89 @@ +package com.thealgorithms.datastructures.lists; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SearchSinglyLinkedListRecursionTest { + + private SearchSinglyLinkedListRecursion list; + + @BeforeEach + public void setUp() { + list = new SearchSinglyLinkedListRecursion(); + } + + @Test + public void testSearchInEmptyList() { + // Test searching for a value in an empty list (should return false) + assertFalse(list.search(1)); + } + + @Test + public void testSearchSingleElementListFound() { + // Insert a single element and search for it + list.insert(5); + assertTrue(list.search(5)); + } + + @Test + public void testSearchSingleElementListNotFound() { + // Insert a single element and search for a non-existent value + list.insert(5); + assertFalse(list.search(10)); + } + + @Test + public void testSearchMultipleElementsListFound() { + // Insert multiple elements and search for a middle value + for (int i = 1; i <= 10; i++) { + list.insert(i); + } + assertTrue(list.search(5)); + } + + @Test + public void testSearchMultipleElementsListFirstElement() { + // Insert multiple elements and search for the first element + for (int i = 1; i <= 10; i++) { + list.insert(i); + } + assertTrue(list.search(1)); + } + + @Test + public void testSearchMultipleElementsListLastElement() { + // Insert multiple elements and search for the last element + for (int i = 1; i <= 10; i++) { + list.insert(i); + } + assertTrue(list.search(10)); + } + + @Test + public void testSearchMultipleElementsListNotFound() { + // Insert multiple elements and search for a non-existent element + for (int i = 1; i <= 10; i++) { + list.insert(i); + } + assertFalse(list.search(15)); + } + + @Test + public void testSearchNegativeValues() { + // Insert positive and negative values and search for a negative value + list.insert(-5); + list.insert(-10); + list.insert(5); + assertTrue(list.search(-10)); + assertFalse(list.search(-3)); + } + + @Test + public void testSearchZeroValue() { + list.insert(0); + assertTrue(list.search(0)); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/lists/SinglyLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/SinglyLinkedListTest.java index a47434083cdb..f80c6b5055f0 100644 --- a/src/test/java/com/thealgorithms/datastructures/lists/SinglyLinkedListTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/SinglyLinkedListTest.java @@ -18,9 +18,9 @@ public class SinglyLinkedListTest { * @return linked list with pre-defined number of nodes */ private SinglyLinkedList createSampleList(int length) { - List nodeList = new ArrayList<>(); + List nodeList = new ArrayList<>(); for (int i = 1; i <= length; i++) { - Node node = new Node(i); + SinglyLinkedListNode node = new SinglyLinkedListNode(i); nodeList.add(node); } @@ -34,10 +34,10 @@ private SinglyLinkedList createSampleList(int length) { @Test void detectLoop() { // List has cycle - Node firstNode = new Node(1); - Node secondNode = new Node(2); - Node thirdNode = new Node(3); - Node fourthNode = new Node(4); + SinglyLinkedListNode firstNode = new SinglyLinkedListNode(1); + SinglyLinkedListNode secondNode = new SinglyLinkedListNode(2); + SinglyLinkedListNode thirdNode = new SinglyLinkedListNode(3); + SinglyLinkedListNode fourthNode = new SinglyLinkedListNode(4); firstNode.next = secondNode; secondNode.next = thirdNode; @@ -112,13 +112,13 @@ void reverseList() { // Reversing the LinkedList using reverseList() method and storing the head of the reversed // linkedlist in a head node The reversed linkedlist will be 4->3->2->1->null - Node head = list.reverseListIter(list.getHead()); + SinglyLinkedListNode head = list.reverseListIter(list.getHead()); // Recording the Nodes after reversing the LinkedList - Node firstNode = head; // 4 - Node secondNode = firstNode.next; // 3 - Node thirdNode = secondNode.next; // 2 - Node fourthNode = thirdNode.next; // 1 + SinglyLinkedListNode firstNode = head; // 4 + SinglyLinkedListNode secondNode = firstNode.next; // 3 + SinglyLinkedListNode thirdNode = secondNode.next; // 2 + SinglyLinkedListNode fourthNode = thirdNode.next; // 1 // Checking whether the LinkedList is reversed or not by comparing the original list and // reversed list nodes @@ -134,10 +134,10 @@ void reverseList() { void reverseListNullPointer() { // Creating a linkedlist with first node assigned to null SinglyLinkedList list = new SinglyLinkedList(); - Node first = list.getHead(); + SinglyLinkedListNode first = list.getHead(); // Reversing the linkedlist - Node head = list.reverseListIter(first); + SinglyLinkedListNode head = list.reverseListIter(first); // checking whether the method works fine if the input is null assertEquals(head, first); @@ -151,10 +151,10 @@ void reverseListTest() { // Reversing the LinkedList using reverseList() method and storing the head of the reversed // linkedlist in a head node - Node head = list.reverseListIter(list.getHead()); + SinglyLinkedListNode head = list.reverseListIter(list.getHead()); // Storing the head in a temp variable, so that we cannot loose the track of head - Node temp = head; + SinglyLinkedListNode temp = head; int i = 20; // This is for the comparison of values of nodes of the reversed linkedlist // Checking whether the reverseList() method performed its task @@ -171,7 +171,7 @@ void recursiveReverseList() { SinglyLinkedList list = createSampleList(5); // Reversing the linked list using reverseList() method - Node head = list.reverseListRec(list.getHead()); + SinglyLinkedListNode head = list.reverseListRec(list.getHead()); // Check if the reversed list is: 5 -> 4 -> 3 -> 2 -> 1 assertEquals(5, head.value); @@ -185,10 +185,10 @@ void recursiveReverseList() { void recursiveReverseListNullPointer() { // Create an empty linked list SinglyLinkedList list = new SinglyLinkedList(); - Node first = list.getHead(); + SinglyLinkedListNode first = list.getHead(); // Reversing the empty linked list - Node head = list.reverseListRec(first); + SinglyLinkedListNode head = list.reverseListRec(first); // Check if the head remains the same (null) assertNull(head); @@ -200,11 +200,11 @@ void recursiveReverseListTest() { SinglyLinkedList list = createSampleList(20); // Reversing the linked list using reverseList() method - Node head = list.reverseListRec(list.getHead()); + SinglyLinkedListNode head = list.reverseListRec(list.getHead()); // Check if the reversed list has the correct values int i = 20; - Node temp = head; + SinglyLinkedListNode temp = head; while (temp != null && i > 0) { assertEquals(i, temp.value); temp = temp.next; diff --git a/src/test/java/com/thealgorithms/datastructures/lists/SortedLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/SortedLinkedListTest.java index 4877e6db4ec4..82e0853da374 100644 --- a/src/test/java/com/thealgorithms/datastructures/lists/SortedLinkedListTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/SortedLinkedListTest.java @@ -4,13 +4,26 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; public class SortedLinkedListTest { + private SortedLinkedList list; + + @BeforeEach + public void setUp() { + list = new SortedLinkedList(); + } + + @Test + public void testInsertIntoEmptyList() { + list.insert(5); + assertEquals("[5]", list.toString()); + } + @Test - public void testInsert() { - SortedLinkedList list = new SortedLinkedList(); + public void testInsertInSortedOrder() { list.insert(5); list.insert(3); list.insert(7); @@ -18,48 +31,99 @@ public void testInsert() { } @Test - public void testDelete() { - SortedLinkedList list = new SortedLinkedList(); + public void testInsertDuplicateValues() { list.insert(5); + list.insert(5); + list.insert(5); + assertEquals("[5, 5, 5]", list.toString()); + } + + @Test + public void testDeleteHeadElement() { + list.insert(1); + list.insert(2); list.insert(3); - list.insert(7); - assertTrue(list.delete(5)); - assertEquals("[3, 7]", list.toString()); - assertFalse(list.delete(10)); + assertTrue(list.delete(1)); + assertEquals("[2, 3]", list.toString()); } @Test - public void testSearch() { - SortedLinkedList list = new SortedLinkedList(); - list.insert(5); + public void testDeleteTailElement() { + list.insert(1); + list.insert(2); list.insert(3); - list.insert(7); - assertTrue(list.search(5)); - assertFalse(list.search(10)); + assertTrue(list.delete(3)); + assertEquals("[1, 2]", list.toString()); + } + + @Test + public void testDeleteMiddleElement() { + list.insert(1); + list.insert(2); + list.insert(3); + assertTrue(list.delete(2)); + assertEquals("[1, 3]", list.toString()); + } + + @Test + public void testDeleteNonexistentElement() { + list.insert(1); + list.insert(2); + assertFalse(list.delete(3)); } + @Test - public void testEmptyList() { - SortedLinkedList list = new SortedLinkedList(); + public void testDeleteFromSingleElementList() { + list.insert(5); + assertTrue(list.delete(5)); assertEquals("[]", list.toString()); + } + + @Test + public void testDeleteFromEmptyList() { assertFalse(list.delete(5)); + } + + @Test + public void testSearchInEmptyList() { assertFalse(list.search(5)); } + + @Test + public void testSearchForExistingElement() { + list.insert(3); + list.insert(1); + list.insert(5); + assertTrue(list.search(3)); + } + + @Test + public void testSearchForNonexistentElement() { + list.insert(3); + list.insert(1); + list.insert(5); + assertFalse(list.search(10)); + } + @Test public void testIsEmptyOnEmptyList() { - SortedLinkedList list = new SortedLinkedList(); assertTrue(list.isEmpty()); } @Test public void testIsEmptyOnNonEmptyList() { - SortedLinkedList list = new SortedLinkedList(); + list.insert(10); + assertFalse(list.isEmpty()); + } + + @Test + public void testIsEmptyAfterInsertion() { list.insert(10); assertFalse(list.isEmpty()); } @Test public void testIsEmptyAfterDeletion() { - SortedLinkedList list = new SortedLinkedList(); list.insert(10); list.delete(10); assertTrue(list.isEmpty()); diff --git a/src/test/java/com/thealgorithms/datastructures/queues/CircularQueueTest.java b/src/test/java/com/thealgorithms/datastructures/queues/CircularQueueTest.java index 71dca8fdb538..55ce9995a32d 100644 --- a/src/test/java/com/thealgorithms/datastructures/queues/CircularQueueTest.java +++ b/src/test/java/com/thealgorithms/datastructures/queues/CircularQueueTest.java @@ -103,4 +103,70 @@ void testSize() { cq.deQueue(); assertEquals(1, cq.size()); } + + @Test + void testCircularWrapAround() { + CircularQueue cq = new CircularQueue<>(3); + cq.enQueue(1); + cq.enQueue(2); + cq.enQueue(3); + + cq.deQueue(); + cq.enQueue(4); + + assertEquals(2, cq.deQueue()); + assertEquals(3, cq.deQueue()); + assertEquals(4, cq.deQueue()); + assertTrue(cq.isEmpty()); + } + + @Test + void testEnQueueDeQueueMultipleTimes() { + CircularQueue cq = new CircularQueue<>(3); + cq.enQueue(1); + cq.enQueue(2); + cq.deQueue(); + cq.enQueue(3); + cq.enQueue(4); + + assertTrue(cq.isFull()); + assertEquals(2, cq.deQueue()); + assertEquals(3, cq.deQueue()); + assertEquals(4, cq.deQueue()); + assertTrue(cq.isEmpty()); + } + + @Test + void testMultipleWrapArounds() { + CircularQueue cq = new CircularQueue<>(3); + cq.enQueue(1); + cq.deQueue(); + cq.enQueue(2); + cq.deQueue(); + cq.enQueue(3); + cq.deQueue(); + cq.enQueue(4); + + assertEquals(4, cq.peek()); + } + + @Test + void testSizeDuringOperations() { + CircularQueue cq = new CircularQueue<>(3); + assertEquals(0, cq.size()); + + cq.enQueue(1); + cq.enQueue(2); + assertEquals(2, cq.size()); + + cq.deQueue(); + assertEquals(1, cq.size()); + + cq.enQueue(3); + cq.enQueue(4); + assertEquals(3, cq.size()); + cq.deQueue(); + cq.deQueue(); + assertEquals(1, cq.size()); + } } diff --git a/src/test/java/com/thealgorithms/datastructures/queues/GenericArrayListQueueTest.java b/src/test/java/com/thealgorithms/datastructures/queues/GenericArrayListQueueTest.java index bb76b8317e62..b19513e19ad9 100644 --- a/src/test/java/com/thealgorithms/datastructures/queues/GenericArrayListQueueTest.java +++ b/src/test/java/com/thealgorithms/datastructures/queues/GenericArrayListQueueTest.java @@ -1,6 +1,7 @@ package com.thealgorithms.datastructures.queues; 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; @@ -13,43 +14,87 @@ void testAdd() { GenericArrayListQueue queue = new GenericArrayListQueue<>(); assertTrue(queue.add(10)); assertTrue(queue.add(20)); + assertEquals(10, queue.peek()); // Ensure the first added element is at the front } @Test void testPeek() { GenericArrayListQueue queue = new GenericArrayListQueue<>(); - assertNull(queue.peek()); + assertNull(queue.peek(), "Peek should return null for an empty queue"); queue.add(10); queue.add(20); - assertEquals(10, queue.peek()); + assertEquals(10, queue.peek(), "Peek should return the first element (10)"); queue.poll(); - assertEquals(20, queue.peek()); + assertEquals(20, queue.peek(), "Peek should return the next element (20) after poll"); } @Test void testPoll() { GenericArrayListQueue queue = new GenericArrayListQueue<>(); - assertNull(queue.poll()); + assertNull(queue.poll(), "Poll should return null for an empty queue"); queue.add(10); queue.add(20); - assertEquals(10, queue.poll()); - assertEquals(20, queue.poll()); - assertNull(queue.poll()); + assertEquals(10, queue.poll(), "Poll should return and remove the first element (10)"); + assertEquals(20, queue.poll(), "Poll should return and remove the next element (20)"); + assertNull(queue.poll(), "Poll should return null when queue is empty after removals"); } @Test void testIsEmpty() { GenericArrayListQueue queue = new GenericArrayListQueue<>(); - assertNull(queue.peek()); - assertNull(queue.poll()); + assertTrue(queue.isEmpty(), "Queue should initially be empty"); queue.add(30); - assertEquals(30, queue.peek()); - assertEquals(30, queue.poll()); - assertNull(queue.peek()); + assertFalse(queue.isEmpty(), "Queue should not be empty after adding an element"); + queue.poll(); + assertTrue(queue.isEmpty(), "Queue should be empty after removing the only element"); + } + + @Test + void testClearQueueAndReuse() { + GenericArrayListQueue queue = new GenericArrayListQueue<>(); + queue.add(5); + queue.add(10); + queue.poll(); + queue.poll(); // Remove all elements + + assertTrue(queue.isEmpty(), "Queue should be empty after all elements are removed"); + assertNull(queue.peek(), "Peek should return null on an empty queue after clear"); + assertTrue(queue.add(15), "Queue should be reusable after being emptied"); + assertEquals(15, queue.peek(), "Newly added element should be accessible in the empty queue"); + } + + @Test + void testOrderMaintained() { + GenericArrayListQueue queue = new GenericArrayListQueue<>(); + queue.add("First"); + queue.add("Second"); + queue.add("Third"); + + assertEquals("First", queue.poll(), "Order should be maintained; expected 'First'"); + assertEquals("Second", queue.poll(), "Order should be maintained; expected 'Second'"); + assertEquals("Third", queue.poll(), "Order should be maintained; expected 'Third'"); + } + + @Test + void testVariousDataTypes() { + GenericArrayListQueue queue = new GenericArrayListQueue<>(); + queue.add(1.1); + queue.add(2.2); + + assertEquals(1.1, queue.peek(), "Queue should handle Double data type correctly"); + assertEquals(1.1, queue.poll(), "Poll should return correct Double value"); + assertEquals(2.2, queue.peek(), "Peek should show next Double value in the queue"); + } + + @Test + void testEmptyPollAndPeekBehavior() { + GenericArrayListQueue queue = new GenericArrayListQueue<>(); + assertNull(queue.peek(), "Peek on an empty queue should return null"); + assertNull(queue.poll(), "Poll on an empty queue should return null"); } } diff --git a/src/test/java/com/thealgorithms/datastructures/queues/QueueByTwoStacksTest.java b/src/test/java/com/thealgorithms/datastructures/queues/QueueByTwoStacksTest.java new file mode 100644 index 000000000000..87f136a84631 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/queues/QueueByTwoStacksTest.java @@ -0,0 +1,69 @@ +package com.thealgorithms.datastructures.queues; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.NoSuchElementException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class QueueByTwoStacksTest { + + private QueueByTwoStacks queue; + + @BeforeEach + public void setUp() { + queue = new QueueByTwoStacks<>(); + } + + @Test + public void testEmptyQueue() { + assertEquals(0, queue.size()); + } + + @Test + public void testEnqueue() { + queue.put(10); + queue.put(20); + assertEquals(2, queue.size()); + } + + @Test + public void testDequeue() { + queue.put(10); + queue.put(20); + queue.put(30); + assertEquals(10, queue.get()); // First item out + assertEquals(20, queue.get()); // Second item out + assertEquals(30, queue.get()); // Third item out + } + + @Test + public void testInterleavedOperations() { + queue.put(10); + queue.put(20); + assertEquals(10, queue.get()); // Dequeue first item + queue.put(30); + assertEquals(20, queue.get()); // Dequeue second item + assertEquals(30, queue.get()); // Dequeue third item + } + + @Test + public void testQueueSize() { + assertEquals(0, queue.size()); + queue.put(1); + assertEquals(1, queue.size()); + queue.put(2); + queue.put(3); + assertEquals(3, queue.size()); + queue.get(); + assertEquals(2, queue.size()); + } + + @Test + public void testEmptyQueueException() { + assertThrows(NoSuchElementException.class, () -> { + queue.get(); // Attempting to dequeue from empty queue + }); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/queues/SlidingWindowMaximumTest.java b/src/test/java/com/thealgorithms/datastructures/queues/SlidingWindowMaximumTest.java new file mode 100644 index 000000000000..e435f9192a88 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/queues/SlidingWindowMaximumTest.java @@ -0,0 +1,50 @@ +package com.thealgorithms.datastructures.queues; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class SlidingWindowMaximumTest { + + @ParameterizedTest + @MethodSource("provideTestCases") + public void testMaxSlidingWindow(int[] nums, int k, int[] expected) { + assertArrayEquals(expected, SlidingWindowMaximum.maxSlidingWindow(nums, k)); + } + + private static Stream provideTestCases() { + return Stream.of( + // Test case 1: Example from the problem statement + Arguments.of(new int[] {1, 3, -1, -3, 5, 3, 6, 7}, 3, new int[] {3, 3, 5, 5, 6, 7}), + + // Test case 2: All elements are the same + Arguments.of(new int[] {4, 4, 4, 4, 4}, 2, new int[] {4, 4, 4, 4}), + + // Test case 3: Window size equals the array length + Arguments.of(new int[] {2, 1, 5, 3, 6}, 5, new int[] {6}), + + // Test case 4: Single element array with window size 1 + Arguments.of(new int[] {7}, 1, new int[] {7}), + + // Test case 5: Window size larger than the array length + Arguments.of(new int[] {1, 2, 3}, 4, new int[] {}), + + // Test case 6: Decreasing sequence + Arguments.of(new int[] {9, 8, 7, 6, 5, 4}, 3, new int[] {9, 8, 7, 6}), + + // Test case 7: Increasing sequence + Arguments.of(new int[] {1, 2, 3, 4, 5}, 2, new int[] {2, 3, 4, 5}), + + // Test case 8: k is zero + Arguments.of(new int[] {1, 3, -1, -3, 5, 3, 6, 7}, 0, new int[] {}), + + // Test case 9: Array with negative numbers + Arguments.of(new int[] {-4, -2, -5, -1, -3}, 3, new int[] {-2, -1, -1}), + + // Test case 10: Empty array + Arguments.of(new int[] {}, 3, new int[] {})); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/queues/TokenBucketTest.java b/src/test/java/com/thealgorithms/datastructures/queues/TokenBucketTest.java new file mode 100644 index 000000000000..959ea8724f8b --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/queues/TokenBucketTest.java @@ -0,0 +1,90 @@ +package com.thealgorithms.datastructures.queues; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class TokenBucketTest { + + @Test + public void testRateLimiter() throws InterruptedException { + TokenBucket bucket = new TokenBucket(5, 1); + for (int i = 0; i < 5; i++) { + assertTrue(bucket.allowRequest()); + } + assertFalse(bucket.allowRequest()); + Thread.sleep(1000); + assertTrue(bucket.allowRequest()); + } + + @Test + public void testRateLimiterWithExceedingRequests() throws InterruptedException { + TokenBucket bucket = new TokenBucket(3, 1); + + for (int i = 0; i < 3; i++) { + assertTrue(bucket.allowRequest()); + } + assertFalse(bucket.allowRequest()); + + Thread.sleep(1000); + assertTrue(bucket.allowRequest()); + assertFalse(bucket.allowRequest()); + } + + @Test + public void testRateLimiterMultipleRefills() throws InterruptedException { + TokenBucket bucket = new TokenBucket(2, 1); + + assertTrue(bucket.allowRequest()); + assertTrue(bucket.allowRequest()); + assertFalse(bucket.allowRequest()); + + Thread.sleep(1000); + assertTrue(bucket.allowRequest()); + + Thread.sleep(1000); + assertTrue(bucket.allowRequest()); + assertFalse(bucket.allowRequest()); + } + + @Test + public void testRateLimiterEmptyBucket() { + TokenBucket bucket = new TokenBucket(0, 1); + + assertFalse(bucket.allowRequest()); + } + + @Test + public void testRateLimiterWithHighRefillRate() throws InterruptedException { + TokenBucket bucket = new TokenBucket(5, 10); + + for (int i = 0; i < 5; i++) { + assertTrue(bucket.allowRequest()); + } + + assertFalse(bucket.allowRequest()); + + Thread.sleep(1000); + + for (int i = 0; i < 5; i++) { + assertTrue(bucket.allowRequest()); + } + } + + @Test + public void testRateLimiterWithSlowRequests() throws InterruptedException { + TokenBucket bucket = new TokenBucket(5, 1); + + for (int i = 0; i < 5; i++) { + assertTrue(bucket.allowRequest()); + } + + Thread.sleep(1000); + assertTrue(bucket.allowRequest()); + + Thread.sleep(2000); + assertTrue(bucket.allowRequest()); + assertTrue(bucket.allowRequest()); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/stacks/NodeStackTest.java b/src/test/java/com/thealgorithms/datastructures/stacks/NodeStackTest.java new file mode 100644 index 000000000000..7ac0d8bc324b --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/stacks/NodeStackTest.java @@ -0,0 +1,73 @@ +package com.thealgorithms.datastructures.stacks; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class NodeStackTest { + + @Test + void testPush() { + NodeStack stack = new NodeStack<>(); + stack.push(10); + stack.push(20); + assertEquals(20, stack.peek(), "Top element should be 20 after pushing 10 and 20."); + } + + @Test + void testPop() { + NodeStack stack = new NodeStack<>(); + stack.push("First"); + stack.push("Second"); + assertEquals("Second", stack.pop(), "Pop should return 'Second', the last pushed element."); + assertEquals("First", stack.pop(), "Pop should return 'First' after 'Second' is removed."); + } + + @Test + void testPopOnEmptyStack() { + NodeStack stack = new NodeStack<>(); + assertThrows(IllegalStateException.class, stack::pop, "Popping an empty stack should throw IllegalStateException."); + } + + @Test + void testPeek() { + NodeStack stack = new NodeStack<>(); + stack.push(5); + stack.push(15); + assertEquals(15, stack.peek(), "Peek should return 15, the top element."); + stack.pop(); + assertEquals(5, stack.peek(), "Peek should return 5 after 15 is popped."); + } + + @Test + void testPeekOnEmptyStack() { + NodeStack stack = new NodeStack<>(); + assertThrows(IllegalStateException.class, stack::peek, "Peeking an empty stack should throw IllegalStateException."); + } + + @Test + void testIsEmpty() { + NodeStack stack = new NodeStack<>(); + assertTrue(stack.isEmpty(), "Newly initialized stack should be empty."); + stack.push('A'); + assertFalse(stack.isEmpty(), "Stack should not be empty after a push operation."); + stack.pop(); + assertTrue(stack.isEmpty(), "Stack should be empty after popping the only element."); + } + + @Test + void testSize() { + NodeStack stack = new NodeStack<>(); + assertEquals(0, stack.size(), "Size of empty stack should be 0."); + stack.push(3); + stack.push(6); + assertEquals(2, stack.size(), "Size should be 2 after pushing two elements."); + stack.pop(); + assertEquals(1, stack.size(), "Size should be 1 after popping one element."); + stack.pop(); + assertEquals(0, stack.size(), "Size should be 0 after popping all elements."); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/stacks/ReverseStackTest.java b/src/test/java/com/thealgorithms/datastructures/stacks/ReverseStackTest.java new file mode 100644 index 000000000000..2e2bc5adae3a --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/stacks/ReverseStackTest.java @@ -0,0 +1,70 @@ +package com.thealgorithms.datastructures.stacks; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Stack; +import org.junit.jupiter.api.Test; + +class ReverseStackTest { + + @Test + void testReverseEmptyStack() { + Stack stack = new Stack<>(); + ReverseStack.reverseStack(stack); + assertTrue(stack.isEmpty(), "Reversing an empty stack should result in an empty stack."); + } + + @Test + void testReverseSingleElementStack() { + Stack stack = new Stack<>(); + stack.push(1); + ReverseStack.reverseStack(stack); + assertEquals(1, stack.peek(), "Reversing a single-element stack should have the same element on top."); + } + + @Test + void testReverseTwoElementStack() { + Stack stack = new Stack<>(); + stack.push(1); + stack.push(2); + ReverseStack.reverseStack(stack); + + assertEquals(1, stack.pop(), "After reversal, the stack's top element should be the first inserted element."); + assertEquals(2, stack.pop(), "After reversal, the stack's next element should be the second inserted element."); + } + + @Test + void testReverseMultipleElementsStack() { + Stack stack = new Stack<>(); + stack.push(1); + stack.push(2); + stack.push(3); + stack.push(4); + + ReverseStack.reverseStack(stack); + + assertEquals(1, stack.pop(), "Stack order after reversal should match the initial push order."); + assertEquals(2, stack.pop()); + assertEquals(3, stack.pop()); + assertEquals(4, stack.pop()); + } + + @Test + void testReverseStackAndVerifySize() { + Stack stack = new Stack<>(); + stack.push(10); + stack.push(20); + stack.push(30); + stack.push(40); + int originalSize = stack.size(); + + ReverseStack.reverseStack(stack); + + assertEquals(originalSize, stack.size(), "Stack size should remain unchanged after reversal."); + assertEquals(10, stack.pop(), "Reversal should place the first inserted element on top."); + assertEquals(20, stack.pop()); + assertEquals(30, stack.pop()); + assertEquals(40, stack.pop()); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/stacks/StackArrayListTest.java b/src/test/java/com/thealgorithms/datastructures/stacks/StackArrayListTest.java index f4c261904bb4..c8811bb3ccc2 100644 --- a/src/test/java/com/thealgorithms/datastructures/stacks/StackArrayListTest.java +++ b/src/test/java/com/thealgorithms/datastructures/stacks/StackArrayListTest.java @@ -30,18 +30,18 @@ void testPeek() { stack.push(10); stack.push(20); - Assertions.assertEquals(20, stack.peek()); - stack.pop(); // Remove 20 - Assertions.assertEquals(10, stack.peek()); + Assertions.assertEquals(20, stack.peek()); // Peek should return the top element + stack.pop(); // Remove top element + Assertions.assertEquals(10, stack.peek()); // Peek should now return the new top element } @Test void testIsEmpty() { - Assertions.assertTrue(stack.isEmpty()); + Assertions.assertTrue(stack.isEmpty()); // Stack should initially be empty stack.push(1); - Assertions.assertFalse(stack.isEmpty()); + Assertions.assertFalse(stack.isEmpty()); // After pushing, stack should not be empty stack.pop(); - Assertions.assertTrue(stack.isEmpty()); + Assertions.assertTrue(stack.isEmpty()); // After popping, stack should be empty again } @Test @@ -50,27 +50,58 @@ void testMakeEmpty() { stack.push(2); stack.push(3); stack.makeEmpty(); - Assertions.assertTrue(stack.isEmpty()); - Assertions.assertEquals(0, stack.size()); + Assertions.assertTrue(stack.isEmpty()); // Stack should be empty after makeEmpty is called + Assertions.assertEquals(0, stack.size()); // Size should be 0 after makeEmpty } @Test void testSize() { - Assertions.assertEquals(0, stack.size()); + Assertions.assertEquals(0, stack.size()); // Initial size should be 0 stack.push(1); stack.push(2); - Assertions.assertEquals(2, stack.size()); + Assertions.assertEquals(2, stack.size()); // Size should reflect number of elements added stack.pop(); - Assertions.assertEquals(1, stack.size()); + Assertions.assertEquals(1, stack.size()); // Size should decrease with elements removed } @Test void testPopEmptyStackThrowsException() { - Assertions.assertThrows(EmptyStackException.class, stack::pop); + Assertions.assertThrows(EmptyStackException.class, stack::pop); // Popping from an empty stack should throw an exception } @Test void testPeekEmptyStackThrowsException() { - Assertions.assertThrows(EmptyStackException.class, stack::peek); + Assertions.assertThrows(EmptyStackException.class, stack::peek); // Peeking into an empty stack should throw an exception + } + + @Test + void testMixedOperations() { + // Testing a mix of push, pop, peek, and size operations in sequence + stack.push(5); + stack.push(10); + stack.push(15); + + Assertions.assertEquals(3, stack.size()); // Size should reflect number of elements + Assertions.assertEquals(15, stack.peek()); // Peek should show last element added + + stack.pop(); // Remove top element + Assertions.assertEquals(10, stack.peek()); // New top should be 10 + Assertions.assertEquals(2, stack.size()); // Size should reflect removal + + stack.push(20); // Add a new element + Assertions.assertEquals(20, stack.peek()); // Top should be the last added element + } + + @Test + void testMultipleMakeEmptyCalls() { + // Ensures calling makeEmpty multiple times does not throw errors or misbehave + stack.push(1); + stack.push(2); + stack.makeEmpty(); + Assertions.assertTrue(stack.isEmpty()); + + stack.makeEmpty(); + Assertions.assertTrue(stack.isEmpty()); + Assertions.assertEquals(0, stack.size()); } } diff --git a/src/test/java/com/thealgorithms/datastructures/stacks/StackArrayTest.java b/src/test/java/com/thealgorithms/datastructures/stacks/StackArrayTest.java index 3cda2f54708e..74de7ad6435a 100644 --- a/src/test/java/com/thealgorithms/datastructures/stacks/StackArrayTest.java +++ b/src/test/java/com/thealgorithms/datastructures/stacks/StackArrayTest.java @@ -21,11 +21,11 @@ void testPushAndPop() { stack.push(4); stack.push(5); - Assertions.assertEquals(5, stack.pop()); // Stack follows LIFO, so 5 should be popped first - Assertions.assertEquals(4, stack.pop()); // Next, 4 should be popped - Assertions.assertEquals(3, stack.pop()); // Followed by 3 - Assertions.assertEquals(2, stack.pop()); // Then 2 - Assertions.assertEquals(1, stack.pop()); // Finally 1 + Assertions.assertEquals(5, stack.pop()); + Assertions.assertEquals(4, stack.pop()); + Assertions.assertEquals(3, stack.pop()); + Assertions.assertEquals(2, stack.pop()); + Assertions.assertEquals(1, stack.pop()); } @Test @@ -34,34 +34,34 @@ void testPeek() { stack.push(20); stack.push(30); - Assertions.assertEquals(30, stack.peek()); // Peek should return 30, the top of the stack - Assertions.assertEquals(3, stack.size()); // Size should remain 3 after peek + Assertions.assertEquals(30, stack.peek()); + Assertions.assertEquals(3, stack.size()); stack.pop(); - Assertions.assertEquals(20, stack.peek()); // After popping, peek should return 20 + Assertions.assertEquals(20, stack.peek()); } @Test void testIsEmpty() { - Assertions.assertTrue(stack.isEmpty()); // Initially, the stack should be empty + Assertions.assertTrue(stack.isEmpty()); stack.push(42); - Assertions.assertFalse(stack.isEmpty()); // After pushing an element, the stack should not be empty + Assertions.assertFalse(stack.isEmpty()); stack.pop(); - Assertions.assertTrue(stack.isEmpty()); // After popping the only element, the stack should be empty again + Assertions.assertTrue(stack.isEmpty()); } @Test void testResizeOnPush() { - StackArray smallStack = new StackArray<>(2); // Start with a small stack size + StackArray smallStack = new StackArray<>(2); smallStack.push(1); smallStack.push(2); - Assertions.assertTrue(smallStack.isFull()); // Initially, the stack should be full + Assertions.assertTrue(smallStack.isFull()); - smallStack.push(3); // This push should trigger a resize - Assertions.assertFalse(smallStack.isFull()); // The stack should no longer be full after resize - Assertions.assertEquals(3, smallStack.size()); // Size should be 3 after pushing 3 elements + smallStack.push(3); + Assertions.assertFalse(smallStack.isFull()); + Assertions.assertEquals(3, smallStack.size()); - Assertions.assertEquals(3, smallStack.pop()); // LIFO behavior check + Assertions.assertEquals(3, smallStack.pop()); Assertions.assertEquals(2, smallStack.pop()); Assertions.assertEquals(1, smallStack.pop()); } @@ -74,13 +74,13 @@ void testResizeOnPop() { stack.push(3); stack.push(4); - stack.pop(); // Removing elements should trigger a resize when less than 1/4 of the stack is used stack.pop(); stack.pop(); - Assertions.assertEquals(1, stack.size()); // After popping, only one element should remain + stack.pop(); + Assertions.assertEquals(1, stack.size()); stack.pop(); - Assertions.assertTrue(stack.isEmpty()); // The stack should be empty now + Assertions.assertTrue(stack.isEmpty()); } @Test @@ -90,32 +90,40 @@ void testMakeEmpty() { stack.push(3); stack.makeEmpty(); - Assertions.assertTrue(stack.isEmpty()); // The stack should be empty after calling makeEmpty - Assertions.assertThrows(IllegalStateException.class, stack::pop); // Popping from empty stack should throw exception + Assertions.assertTrue(stack.isEmpty()); + Assertions.assertThrows(IllegalStateException.class, stack::pop); } @Test void testPopEmptyStackThrowsException() { - Assertions.assertThrows(IllegalStateException.class, stack::pop); // Popping from an empty stack should throw an exception + Assertions.assertThrows(IllegalStateException.class, stack::pop); } @Test void testPeekEmptyStackThrowsException() { - Assertions.assertThrows(IllegalStateException.class, stack::peek); // Peeking into an empty stack should throw an exception + Assertions.assertThrows(IllegalStateException.class, stack::peek); } @Test void testConstructorWithInvalidSizeThrowsException() { - Assertions.assertThrows(IllegalArgumentException.class, () -> new StackArray<>(0)); // Size 0 is invalid - Assertions.assertThrows(IllegalArgumentException.class, () -> new StackArray<>(-5)); // Negative size is invalid + Assertions.assertThrows(IllegalArgumentException.class, () -> new StackArray<>(0)); + Assertions.assertThrows(IllegalArgumentException.class, () -> new StackArray<>(-5)); } @Test void testDefaultConstructor() { - StackArray defaultStack = new StackArray<>(); // Using default constructor - Assertions.assertEquals(0, defaultStack.size()); // Initially, size should be 0 + StackArray defaultStack = new StackArray<>(); + Assertions.assertEquals(0, defaultStack.size()); defaultStack.push(1); - Assertions.assertEquals(1, defaultStack.size()); // After pushing, size should be 1 + Assertions.assertEquals(1, defaultStack.size()); + } + + @Test + void testToString() { + stack.push(1); + stack.push(2); + stack.push(3); + Assertions.assertEquals("StackArray [1, 2, 3]", stack.toString()); } } diff --git a/src/test/java/com/thealgorithms/datastructures/stacks/StackOfLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/stacks/StackOfLinkedListTest.java new file mode 100644 index 000000000000..58af66bc38f4 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/stacks/StackOfLinkedListTest.java @@ -0,0 +1,121 @@ +package com.thealgorithms.datastructures.stacks; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.NoSuchElementException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class StackOfLinkedListTest { + + private LinkedListStack stack; + + @BeforeEach + public void setUp() { + stack = new LinkedListStack(); + } + + @Test + public void testPushAndPeek() { + stack.push(1); + stack.push(2); + stack.push(3); + + assertEquals(3, stack.peek(), "Peek should return the last pushed value"); + assertEquals(3, stack.getSize(), "Size should reflect the number of elements"); + } + + @Test + public void testPop() { + stack.push(1); + stack.push(2); + stack.push(3); + + assertEquals(3, stack.pop(), "Pop should return the last pushed value"); + assertEquals(2, stack.pop(), "Pop should return the next last pushed value"); + assertEquals(1, stack.pop(), "Pop should return the first pushed value"); + assertTrue(stack.isEmpty(), "Stack should be empty after popping all elements"); + } + + @Test + public void testPopEmptyStack() { + org.junit.jupiter.api.Assertions.assertThrows(NoSuchElementException.class, () -> stack.pop(), "Popping from an empty stack should throw NoSuchElementException"); + } + + @Test + public void testPeekEmptyStack() { + org.junit.jupiter.api.Assertions.assertThrows(NoSuchElementException.class, () -> stack.peek(), "Peeking into an empty stack should throw NoSuchElementException"); + } + + @Test + public void testIsEmpty() { + assertTrue(stack.isEmpty(), "Newly created stack should be empty"); + + stack.push(1); + assertFalse(stack.isEmpty(), "Stack should not be empty after pushing an element"); + + stack.pop(); + assertTrue(stack.isEmpty(), "Stack should be empty after popping the only element"); + } + + @Test + public void testToString() { + stack.push(1); + stack.push(2); + stack.push(3); + + assertEquals("3->2->1", stack.toString(), "String representation of stack should match the expected format"); + } + + @Test + public void testMultiplePushesAndPops() { + stack.push(5); + stack.push(10); + stack.push(15); + + assertEquals(15, stack.pop(), "Pop should return the last pushed value"); + assertEquals(10, stack.peek(), "Peek should return the new top value after popping"); + assertEquals(10, stack.pop(), "Pop should return the next last pushed value"); + assertEquals(5, stack.pop(), "Pop should return the first pushed value"); + assertTrue(stack.isEmpty(), "Stack should be empty after popping all elements"); + } + + @Test + public void testGetSize() { + assertEquals(0, stack.getSize(), "Size of an empty stack should be zero"); + stack.push(1); + stack.push(2); + assertEquals(2, stack.getSize(), "Size should reflect the number of elements"); + stack.pop(); + assertEquals(1, stack.getSize(), "Size should decrease with each pop"); + } + + @Test + public void testSizeAfterClearingStack() { + stack.push(1); + stack.push(2); + stack.push(3); + + // Manually clear the stack + while (!stack.isEmpty()) { + stack.pop(); + } + assertTrue(stack.isEmpty(), "Stack should be empty after clearing"); + assertEquals(0, stack.getSize(), "Size should be zero after clearing the stack"); + } + + @Test + public void testSequentialPushAndPop() { + for (int i = 1; i <= 100; i++) { + stack.push(i); + } + assertEquals(100, stack.getSize(), "Size should be 100 after pushing 100 elements"); + + for (int i = 100; i >= 1; i--) { + assertEquals(i, stack.pop(), "Popping should return values in LIFO order"); + } + assertTrue(stack.isEmpty(), "Stack should be empty after popping all elements"); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/trees/AVLTreeTest.java b/src/test/java/com/thealgorithms/datastructures/trees/AVLTreeTest.java new file mode 100644 index 000000000000..6aa5dc9e22ed --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/trees/AVLTreeTest.java @@ -0,0 +1,101 @@ +package com.thealgorithms.datastructures.trees; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class AVLTreeTest { + private AVLTree avlTree; + + @BeforeEach + public void setUp() { + avlTree = new AVLTree(); + } + + @Test + public void testInsert() { + assertTrue(avlTree.insert(10)); + assertTrue(avlTree.insert(20)); + assertTrue(avlTree.insert(5)); + assertFalse(avlTree.insert(10)); // Duplicate + } + + @Test + public void testSearch() { + avlTree.insert(15); + avlTree.insert(25); + assertTrue(avlTree.search(15)); + assertFalse(avlTree.search(30)); // Not in the tree + } + + @Test + public void testDeleteLeafNode() { + avlTree.insert(10); + avlTree.insert(20); + avlTree.insert(30); + avlTree.delete(30); + assertFalse(avlTree.search(30)); + } + + @Test + public void testDeleteNodeWithOneChild() { + avlTree.insert(20); + avlTree.insert(10); + avlTree.insert(30); + avlTree.delete(10); + assertFalse(avlTree.search(10)); + } + + @Test + public void testDeleteNodeWithTwoChildren() { + avlTree.insert(20); + avlTree.insert(10); + avlTree.insert(30); + avlTree.insert(25); + avlTree.delete(20); + assertFalse(avlTree.search(20)); + assertTrue(avlTree.search(30)); + assertTrue(avlTree.search(25)); + } + + @Test + public void testReturnBalance() { + avlTree.insert(10); + avlTree.insert(20); + avlTree.insert(5); + List balances = avlTree.returnBalance(); + assertEquals(3, balances.size()); // There should be 3 nodes + assertEquals(0, balances.get(0)); // Balance for node 5 + assertEquals(0, balances.get(1)); // Balance for node 10 + assertEquals(0, balances.get(2)); // Balance for node 20 + } + + @Test + public void testInsertAndRebalance() { + avlTree.insert(30); + avlTree.insert(20); + avlTree.insert(10); // This should cause a right rotation + assertTrue(avlTree.search(20)); + assertTrue(avlTree.search(10)); + assertTrue(avlTree.search(30)); + } + + @Test + public void testComplexInsertionAndDeletion() { + avlTree.insert(30); + avlTree.insert(20); + avlTree.insert(10); + avlTree.insert(25); + avlTree.insert(5); + avlTree.insert(15); + + avlTree.delete(20); // Test deletion + assertFalse(avlTree.search(20)); + assertTrue(avlTree.search(30)); + assertTrue(avlTree.search(25)); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeTest.java b/src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeTest.java index b153c5d667de..d6581fb8c4e8 100644 --- a/src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeTest.java +++ b/src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeTest.java @@ -1,76 +1,78 @@ package com.thealgorithms.datastructures.trees; -import static org.junit.jupiter.api.Assertions.assertEquals; - +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +/** + * Unit tests for the BinaryTree class. + */ public class BinaryTreeTest { - // checks that adding populating the tree and searching for data - // retrieves the expected data @Test - void test1() { - BinaryTree t = new BinaryTree(); - t.put(3); - t.put(5); - t.put(7); - t.put(9); - t.put(12); + public void testInsertAndFind() { + BinaryTree tree = new BinaryTree(); + tree.put(3); + tree.put(5); + tree.put(7); + tree.put(9); + tree.put(12); - assertEquals(t.find(5).data, 5); - assertEquals(t.find(7).data, 7); + Assertions.assertNotNull(tree.find(5), "Node with value 5 should exist"); + Assertions.assertEquals(5, tree.find(5).data, "Value of the found node should be 5"); + Assertions.assertEquals(7, tree.find(7).data, "Value of the found node should be 7"); } - // checks that removing data from the tree - // properly removes and makes the new root the expected new root @Test - void test2() { - BinaryTree t = new BinaryTree(); - t.put(3); - t.put(5); - t.put(7); - t.put(9); - t.put(12); - t.remove(3); - t.remove(5); - t.remove(7); + public void testRemove() { + BinaryTree tree = new BinaryTree(); + tree.put(3); + tree.put(5); + tree.put(7); + tree.put(9); + tree.put(12); + tree.remove(3); + tree.remove(5); + tree.remove(7); - assertEquals(t.getRoot().data, 9); + Assertions.assertNotNull(tree.getRoot(), "Root should not be null after removals"); + if (tree.getRoot() != null) { + Assertions.assertEquals(9, tree.getRoot().data, "Root value should be 9 after removals"); + } else { + Assertions.fail("Root should not be null after removals, but it is."); + } } - // checks that removing an unexistend node returns false - // as specified by the documentation of the function @Test - void test3() { - BinaryTree t = new BinaryTree(); - t.put(3); - t.put(5); - t.put(7); - t.put(9); - t.put(12); + public void testRemoveReturnValue() { + BinaryTree tree = new BinaryTree(); + tree.put(3); + tree.put(5); + tree.put(7); + tree.put(9); + tree.put(12); - assertEquals(t.remove(9), true); - assertEquals(t.remove(398745987), false); + Assertions.assertTrue(tree.remove(9), "Removing existing node 9 should return true"); + Assertions.assertFalse(tree.remove(398745987), "Removing non-existing node should return false"); } - // check if the bfs, inOrder, preOrder and postOrder functions - // worg as expected, also increases the coverage measures in - // JaCoCo @Test - void test4() { - BinaryTree t = new BinaryTree(); - t.put(3); - t.put(5); - t.put(7); - t.put(9); - t.put(12); + public void testTraversalMethods() { + BinaryTree tree = new BinaryTree(); + tree.put(3); + tree.put(5); + tree.put(7); + tree.put(9); + tree.put(12); + + // Testing traversal methods + tree.bfs(tree.getRoot()); + tree.inOrder(tree.getRoot()); + tree.preOrder(tree.getRoot()); + tree.postOrder(tree.getRoot()); - t.bfs(t.find(12)); - t.inOrder(t.getRoot()); - t.preOrder(t.getRoot()); - t.postOrder(t.getRoot()); + Assertions.assertTrue(tree.remove(9), "Removing existing node 9 should return true"); + Assertions.assertFalse(tree.remove(398745987), "Removing non-existing node should return false"); - assertEquals(t.remove(9), true); - assertEquals(t.remove(398745987), false); + Assertions.assertNotNull(tree.getRoot(), "Root should not be null after operations"); } } diff --git a/src/test/java/com/thealgorithms/datastructures/trees/BoundaryTraversalTest.java b/src/test/java/com/thealgorithms/datastructures/trees/BoundaryTraversalTest.java new file mode 100644 index 000000000000..515dac88ce09 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/trees/BoundaryTraversalTest.java @@ -0,0 +1,108 @@ +package com.thealgorithms.datastructures.trees; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * + */ +public class BoundaryTraversalTest { + + @Test + public void testNullRoot() { + assertEquals(Collections.emptyList(), BoundaryTraversal.boundaryTraversal(null)); + assertEquals(Collections.emptyList(), BoundaryTraversal.iterativeBoundaryTraversal(null)); + } + + @Test + public void testSingleNodeTree() { + final BinaryTree.Node root = new BinaryTree.Node(1); + + List expected = List.of(1); + + assertEquals(expected, BoundaryTraversal.boundaryTraversal(root)); + assertEquals(expected, BoundaryTraversal.iterativeBoundaryTraversal(root)); + } + + /* + 1 + / \ + 2 3 + / \ / \ + 4 5 6 7 + + */ + @Test + public void testCompleteBinaryTree() { + final BinaryTree.Node root = TreeTestUtils.createTree(new Integer[] {1, 2, 3, 4, 5, 6, 7}); + + List expected = List.of(1, 2, 4, 5, 6, 7, 3); + + assertEquals(expected, BoundaryTraversal.boundaryTraversal(root)); + assertEquals(expected, BoundaryTraversal.iterativeBoundaryTraversal(root)); + } + + /* + 1 + / \ + 2 7 + / \ + 3 8 + \ / + 4 9 + / \ + 5 6 + / \ + 10 11 + */ + @Test + public void testBoundaryTraversal() { + final BinaryTree.Node root = TreeTestUtils.createTree(new Integer[] {1, 2, 7, 3, null, null, 8, null, 4, 9, null, 5, 6, 10, 11}); + + List expected = List.of(1, 2, 3, 4, 5, 6, 10, 11, 9, 8, 7); + + assertEquals(expected, BoundaryTraversal.boundaryTraversal(root)); + assertEquals(expected, BoundaryTraversal.iterativeBoundaryTraversal(root)); + } + + /* + 1 + / + 2 + / + 3 + / + 4 + */ + @Test + public void testLeftSkewedTree() { + final BinaryTree.Node root = TreeTestUtils.createTree(new Integer[] {1, 2, null, 3, null, 4, null}); + + List expected = List.of(1, 2, 3, 4); + + assertEquals(expected, BoundaryTraversal.boundaryTraversal(root)); + assertEquals(expected, BoundaryTraversal.iterativeBoundaryTraversal(root)); + } + + /* + 5 + \ + 6 + \ + 7 + \ + 8 + */ + @Test + public void testRightSkewedTree() { + final BinaryTree.Node root = TreeTestUtils.createTree(new Integer[] {5, null, 6, null, 7, null, 8}); + + List expected = List.of(5, 6, 7, 8); + + assertEquals(expected, BoundaryTraversal.boundaryTraversal(root)); + assertEquals(expected, BoundaryTraversal.iterativeBoundaryTraversal(root)); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/trees/QuadTreeTest.java b/src/test/java/com/thealgorithms/datastructures/trees/QuadTreeTest.java new file mode 100644 index 000000000000..62b86da214db --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/trees/QuadTreeTest.java @@ -0,0 +1,58 @@ +package com.thealgorithms.datastructures.trees; + +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class QuadTreeTest { + int quadTreeCapacity = 4; + BoundingBox boundingBox = new BoundingBox(new Point(0, 0), 500); + QuadTree quadTree = new QuadTree(boundingBox, quadTreeCapacity); + + @Test + public void testNullPointInsertIntoQuadTree() { + Assertions.assertFalse(quadTree.insert(null)); + } + + @Test + public void testInsertIntoQuadTree() { + Assertions.assertTrue(quadTree.insert(new Point(10, -10))); + Assertions.assertTrue(quadTree.insert(new Point(-10, 10))); + Assertions.assertTrue(quadTree.insert(new Point(-10, -10))); + Assertions.assertTrue(quadTree.insert(new Point(10, 10))); + Assertions.assertFalse(quadTree.insert(new Point(1050, 1050))); + } + + @Test + public void testInsertIntoQuadTreeAndSubDivide() { + Assertions.assertTrue(quadTree.insert(new Point(10, -10))); + Assertions.assertTrue(quadTree.insert(new Point(-10, 10))); + Assertions.assertTrue(quadTree.insert(new Point(-10, -10))); + Assertions.assertTrue(quadTree.insert(new Point(10, 10))); + Assertions.assertTrue(quadTree.insert(new Point(-100, 100))); + Assertions.assertTrue(quadTree.insert(new Point(100, -101))); + Assertions.assertTrue(quadTree.insert(new Point(-100, -100))); + Assertions.assertTrue(quadTree.insert(new Point(100, 100))); + } + + @Test + public void testQueryInQuadTree() { + quadTree.insert(new Point(10, -10)); + quadTree.insert(new Point(-10, 10)); + quadTree.insert(new Point(-10, -10)); + quadTree.insert(new Point(10, 10)); + quadTree.insert(new Point(-100, 100)); + quadTree.insert(new Point(100, -100)); + quadTree.insert(new Point(-100, -100)); + quadTree.insert(new Point(100, 100)); + + List points = quadTree.query(new BoundingBox(new Point(0, 0), 100)); + Assertions.assertEquals(8, points.size()); + + points = quadTree.query(new BoundingBox(new Point(5, 5), 5)); + Assertions.assertEquals(1, points.size()); + + points = quadTree.query(new BoundingBox(new Point(-200, -200), 5)); + Assertions.assertEquals(0, points.size()); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/trees/TrieImpTest.java b/src/test/java/com/thealgorithms/datastructures/trees/TrieTest.java similarity index 69% rename from src/test/java/com/thealgorithms/datastructures/trees/TrieImpTest.java rename to src/test/java/com/thealgorithms/datastructures/trees/TrieTest.java index 600fdef0a718..9348118bb343 100644 --- a/src/test/java/com/thealgorithms/datastructures/trees/TrieImpTest.java +++ b/src/test/java/com/thealgorithms/datastructures/trees/TrieTest.java @@ -1,17 +1,21 @@ package com.thealgorithms.datastructures.trees; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class TrieImpTest { - private TrieImp trie; +public class TrieTest { + private static final List WORDS = List.of("Apple", "App", "app", "APPLE"); + + private Trie trie; @BeforeEach public void setUp() { - trie = new TrieImp(); + trie = new Trie(); } @Test @@ -66,11 +70,26 @@ public void testInsertAndSearchPrefix() { } @Test - public void testIsValidWord() { - assertTrue(TrieImp.isValid("validword"), "Word should be valid (only lowercase letters)."); - assertFalse(TrieImp.isValid("InvalidWord"), "Word should be invalid (contains uppercase letters)."); - assertFalse(TrieImp.isValid("123abc"), "Word should be invalid (contains numbers)."); - assertFalse(TrieImp.isValid("hello!"), "Word should be invalid (contains special characters)."); - assertFalse(TrieImp.isValid(""), "Empty string should be invalid."); + public void testCountWords() { + Trie trie = createTrie(); + assertEquals(WORDS.size(), trie.countWords(), "Count words should return the correct number of words."); + } + + @Test + public void testStartsWithPrefix() { + Trie trie = createTrie(); + assertTrue(trie.startsWithPrefix("App"), "Starts with prefix should return true."); + } + + @Test + public void testCountWordsWithPrefix() { + Trie trie = createTrie(); + assertEquals(2, trie.countWordsWithPrefix("App"), "Count words with prefix should return 2."); + } + + private Trie createTrie() { + Trie trie = new Trie(); + WORDS.forEach(trie::insert); + return trie; } } diff --git a/src/test/java/com/thealgorithms/divideandconquer/CountingInversionsTest.java b/src/test/java/com/thealgorithms/divideandconquer/CountingInversionsTest.java new file mode 100644 index 000000000000..d12614d6fd06 --- /dev/null +++ b/src/test/java/com/thealgorithms/divideandconquer/CountingInversionsTest.java @@ -0,0 +1,32 @@ +package com.thealgorithms.divideandconquer; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class CountingInversionsTest { + + @Test + public void testCountInversions() { + int[] arr = {2, 3, 8, 6, 1}; + assertEquals(5, CountingInversions.countInversions(arr)); + } + + @Test + public void testNoInversions() { + int[] arr = {1, 2, 3, 4, 5}; + assertEquals(0, CountingInversions.countInversions(arr)); + } + + @Test + public void testSingleElement() { + int[] arr = {1}; + assertEquals(0, CountingInversions.countInversions(arr)); + } + + @Test + public void testAllInversions() { + int[] arr = {5, 4, 3, 2, 1}; + assertEquals(10, CountingInversions.countInversions(arr)); + } +} diff --git a/src/test/java/com/thealgorithms/divideandconquer/MedianOfTwoSortedArraysTest.java b/src/test/java/com/thealgorithms/divideandconquer/MedianOfTwoSortedArraysTest.java new file mode 100644 index 000000000000..6cfbc2379600 --- /dev/null +++ b/src/test/java/com/thealgorithms/divideandconquer/MedianOfTwoSortedArraysTest.java @@ -0,0 +1,41 @@ +package com.thealgorithms.divideandconquer; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class MedianOfTwoSortedArraysTest { + + @ParameterizedTest + @MethodSource("provideTestCases") + void testFindMedianSortedArrays(int[] nums1, int[] nums2, double expectedMedian) { + assertEquals(expectedMedian, MedianOfTwoSortedArrays.findMedianSortedArrays(nums1, nums2)); + } + + private static Stream provideTestCases() { + return Stream.of( + // Test case 1: Arrays of equal length + Arguments.of(new int[] {1, 3}, new int[] {2, 4}, 2.5), + + // Test case 2: Arrays of different lengths + Arguments.of(new int[] {1, 3}, new int[] {2}, 2.0), + + // Test case 3: Arrays with even total length + Arguments.of(new int[] {1, 2, 8}, new int[] {3, 4, 5, 6, 7}, 4.5), + + // Test case 4: Arrays with odd total length + Arguments.of(new int[] {1, 2, 8}, new int[] {3, 4, 5}, 3.5), + + // Test case 5: Single element arrays + Arguments.of(new int[] {1}, new int[] {3}, 2.0), + + // Test case 6: Empty arrays + Arguments.of(new int[] {}, new int[] {0}, 0.0), + + // Test case 7: Same element arrays + Arguments.of(new int[] {2, 2, 2}, new int[] {2, 2, 2}, 2.0)); + } +} diff --git a/src/test/java/com/thealgorithms/divideandconquer/TilingProblemTest.java b/src/test/java/com/thealgorithms/divideandconquer/TilingProblemTest.java new file mode 100644 index 000000000000..720e425f5ea3 --- /dev/null +++ b/src/test/java/com/thealgorithms/divideandconquer/TilingProblemTest.java @@ -0,0 +1,15 @@ +package com.thealgorithms.divideandconquer; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.Test; + +public class TilingProblemTest { + + @Test + public void testTilingSize2() { + int[][] expected = {{1, 1}, {1, 0}}; + int[][] result = TilingProblem.solveTiling(2, 1, 1); + assertArrayEquals(expected, result); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/AbbreviationTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/AbbreviationTest.java new file mode 100644 index 000000000000..4e36edbd7774 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/AbbreviationTest.java @@ -0,0 +1,44 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class AbbreviationTest { + + @ParameterizedTest + @MethodSource("provideTestCases") + public void testAbbreviation(String a, String b, boolean expected) { + assertEquals(expected, Abbreviation.abbr(a, b)); + } + + private static Stream provideTestCases() { + return Stream.of( + // Example test case from problem description + Arguments.of("daBcd", "ABC", Boolean.TRUE), + + // Test case where transformation is impossible + Arguments.of("dBcd", "ABC", Boolean.FALSE), + + // Test case with exact match (all uppercase) + Arguments.of("ABC", "ABC", Boolean.TRUE), + + // Test case where input string contains all required letters plus extra lowercase letters + Arguments.of("aAbBcC", "ABC", Boolean.TRUE), + + // Test case with only lowercase letters in input + Arguments.of("abcd", "ABCD", Boolean.TRUE), + + // Test case with an empty second string (b) + Arguments.of("abc", "", Boolean.TRUE), + + // Test case with an empty first string (a) but non-empty second string (b) + Arguments.of("", "A", Boolean.FALSE), + + // Complex case with interleaved letters + Arguments.of("daBcAbCd", "ABCD", Boolean.FALSE)); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/AllConstructTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/AllConstructTest.java new file mode 100644 index 000000000000..012876921c15 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/AllConstructTest.java @@ -0,0 +1,40 @@ +package com.thealgorithms.dynamicprogramming; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class AllConstructTest { + + @Test + public void testAllConstructBasic() { + List> expected = singletonList(Arrays.asList("he", "l", "l", "o")); + List> result = AllConstruct.allConstruct("hello", Arrays.asList("he", "l", "o")); + assertEquals(expected, result); + } + + @Test + public void testAllConstructMultipleWays() { + List> expected = Arrays.asList(Arrays.asList("purp", "le"), Arrays.asList("p", "ur", "p", "le")); + List> result = AllConstruct.allConstruct("purple", Arrays.asList("purp", "p", "ur", "le", "purpl")); + assertEquals(expected, result); + } + + @Test + public void testAllConstructNoWays() { + List> expected = emptyList(); + List> result = AllConstruct.allConstruct("abcdef", Arrays.asList("gh", "ijk")); + assertEquals(expected, result); + } + + @Test + public void testAllConstructEmptyTarget() { + List> expected = singletonList(emptyList()); + List> result = AllConstruct.allConstruct("", Arrays.asList("a", "b", "c")); + assertEquals(expected, result); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmaskTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmaskTest.java new file mode 100644 index 000000000000..eadc43ce59c5 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmaskTest.java @@ -0,0 +1,55 @@ +package com.thealgorithms.dynamicprogramming; + +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public final class AssignmentUsingBitmaskTest { + + @Test + public void testCountNoOfWays() { + int totalTasks = 5; + + List> taskPerformed = Arrays.asList(Arrays.asList(1, 3, 4), Arrays.asList(1, 2, 5), Arrays.asList(3, 4)); + + AssignmentUsingBitmask assignment = new AssignmentUsingBitmask(taskPerformed, totalTasks); + int ways = assignment.countNoOfWays(); + assertEquals(10, ways); + } + + @Test + public void testNoPossibleAssignments() { + int totalTasks = 3; + + List> taskPerformed = Arrays.asList(singletonList(2), singletonList(3)); + + AssignmentUsingBitmask assignment = new AssignmentUsingBitmask(taskPerformed, totalTasks); + int ways = assignment.countNoOfWays(); + assertEquals(1, ways); + } + + @Test + public void testSinglePersonMultipleTasks() { + int totalTasks = 3; + + List> taskPerformed = singletonList(Arrays.asList(1, 2, 3)); + + AssignmentUsingBitmask assignment = new AssignmentUsingBitmask(taskPerformed, totalTasks); + int ways = assignment.countNoOfWays(); + assertEquals(3, ways); + } + + @Test + public void testMultiplePeopleSingleTask() { + int totalTasks = 1; + + List> taskPerformed = Arrays.asList(singletonList(1), singletonList(1)); + + AssignmentUsingBitmask assignment = new AssignmentUsingBitmask(taskPerformed, totalTasks); + int ways = assignment.countNoOfWays(); + assertEquals(0, ways); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/BoundaryFillTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/BoundaryFillTest.java new file mode 100644 index 000000000000..4aa412731a10 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/BoundaryFillTest.java @@ -0,0 +1,66 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class BoundaryFillTest { + + private int[][] image; + + @BeforeEach + void setUp() { + image = new int[][] {{0, 0, 0, 0, 0, 0, 0}, {0, 3, 3, 3, 3, 0, 0}, {0, 3, 0, 0, 3, 0, 0}, {0, 3, 0, 0, 3, 3, 3}, {0, 3, 3, 3, 0, 0, 3}, {0, 0, 0, 3, 0, 0, 3}, {0, 0, 0, 3, 3, 3, 3}}; + } + + @Test + void testGetPixel() { + assertEquals(3, BoundaryFill.getPixel(image, 1, 1)); + assertEquals(0, BoundaryFill.getPixel(image, 2, 2)); + assertEquals(3, BoundaryFill.getPixel(image, 4, 3)); + } + + @Test + void testPutPixel() { + BoundaryFill.putPixel(image, 2, 2, 5); + assertEquals(5, BoundaryFill.getPixel(image, 2, 2)); + + BoundaryFill.putPixel(image, 0, 0, 7); + assertEquals(7, BoundaryFill.getPixel(image, 0, 0)); + } + + @Test + void testBoundaryFill() { + BoundaryFill.boundaryFill(image, 2, 2, 5, 3); + + int[][] expectedImage = {{0, 0, 0, 0, 0, 0, 0}, {0, 3, 3, 3, 3, 0, 0}, {0, 3, 5, 5, 3, 0, 0}, {0, 3, 5, 5, 3, 3, 3}, {0, 3, 3, 3, 5, 5, 3}, {0, 0, 0, 3, 5, 5, 3}, {0, 0, 0, 3, 3, 3, 3}}; + + for (int i = 0; i < image.length; i++) { + assertArrayEquals(expectedImage[i], image[i]); + } + } + + @Test + void testBoundaryFillEdgeCase() { + BoundaryFill.boundaryFill(image, 1, 1, 3, 3); + + int[][] expectedImage = {{0, 0, 0, 0, 0, 0, 0}, {0, 3, 3, 3, 3, 0, 0}, {0, 3, 0, 0, 3, 0, 0}, {0, 3, 0, 0, 3, 3, 3}, {0, 3, 3, 3, 0, 0, 3}, {0, 0, 0, 3, 0, 0, 3}, {0, 0, 0, 3, 3, 3, 3}}; + + for (int i = 0; i < image.length; i++) { + assertArrayEquals(expectedImage[i], image[i]); + } + } + + @Test + void testBoundaryFillInvalidCoordinates() { + BoundaryFill.boundaryFill(image, -1, -1, 5, 3); + + int[][] expectedImage = {{0, 0, 0, 0, 0, 0, 0}, {0, 3, 3, 3, 3, 0, 0}, {0, 3, 0, 0, 3, 0, 0}, {0, 3, 0, 0, 3, 3, 3}, {0, 3, 3, 3, 0, 0, 3}, {0, 0, 0, 3, 0, 0, 3}, {0, 0, 0, 3, 3, 3, 3}}; + + for (int i = 0; i < image.length; i++) { + assertArrayEquals(expectedImage[i], image[i]); + } + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsackTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsackTest.java new file mode 100644 index 000000000000..ef96f16e04f7 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsackTest.java @@ -0,0 +1,96 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class BruteForceKnapsackTest { + + @Test + void testKnapSackBasicCase() { + int[] val = {60, 100, 120}; + int[] wt = {10, 20, 30}; + int w = 50; + int n = val.length; + + // The expected result for this case is 220 (items 2 and 3 are included) + assertEquals(220, BruteForceKnapsack.knapSack(w, wt, val, n)); + } + + @Test + void testKnapSackNoItems() { + int[] val = {}; + int[] wt = {}; + int w = 50; + int n = val.length; + + // With no items, the maximum value should be 0 + assertEquals(0, BruteForceKnapsack.knapSack(w, wt, val, n)); + } + + @Test + void testKnapSackZeroCapacity() { + int[] val = {60, 100, 120}; + int[] wt = {10, 20, 30}; + int w = 0; + int n = val.length; + + // With a knapsack of 0 capacity, no items can be included, so the value is 0 + assertEquals(0, BruteForceKnapsack.knapSack(w, wt, val, n)); + } + + @Test + void testKnapSackSingleItemFits() { + int[] val = {100}; + int[] wt = {20}; + int w = 30; + int n = val.length; + + // Only one item, and it fits in the knapsack, so the result is 100 + assertEquals(100, BruteForceKnapsack.knapSack(w, wt, val, n)); + } + + @Test + void testKnapSackSingleItemDoesNotFit() { + int[] val = {100}; + int[] wt = {20}; + int w = 10; + int n = val.length; + + // Single item does not fit in the knapsack, so the result is 0 + assertEquals(0, BruteForceKnapsack.knapSack(w, wt, val, n)); + } + + @Test + void testKnapSackAllItemsFit() { + int[] val = {20, 30, 40}; + int[] wt = {1, 2, 3}; + int w = 6; + int n = val.length; + + // All items fit into the knapsack, so the result is the sum of all values (20 + 30 + 40 = 90) + assertEquals(90, BruteForceKnapsack.knapSack(w, wt, val, n)); + } + + @Test + void testKnapSackNoneFit() { + int[] val = {100, 200, 300}; + int[] wt = {100, 200, 300}; + int w = 50; + int n = val.length; + + // None of the items fit into the knapsack, so the result is 0 + assertEquals(0, BruteForceKnapsack.knapSack(w, wt, val, n)); + } + + @Test + void testKnapSackSomeItemsFit() { + int[] val = {60, 100, 120}; + int[] wt = {10, 20, 30}; + int w = 40; + int n = val.length; + + // Here, only the 2nd and 1st items should be included for a total value of 160 + assertEquals(180, BruteForceKnapsack.knapSack(w, wt, val, n)); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/CoinChangeTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/CoinChangeTest.java new file mode 100644 index 000000000000..10bc6600ae1e --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/CoinChangeTest.java @@ -0,0 +1,80 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class CoinChangeTest { + + @Test + void testChangeBasic() { + int amount = 12; + int[] coins = {2, 4, 5}; + + assertEquals(5, CoinChange.change(coins, amount)); + } + + @Test + void testChangeNoCoins() { + int amount = 12; + int[] coins = {}; + + assertEquals(0, CoinChange.change(coins, amount)); + } + + @Test + void testChangeNoAmount() { + int amount = 0; + int[] coins = {2, 4, 5}; + + assertEquals(1, CoinChange.change(coins, amount)); + } + + @Test + void testChangeImpossibleAmount() { + int amount = 3; + int[] coins = {2, 4, 5}; + + assertEquals(0, CoinChange.change(coins, amount)); + } + + @Test + void testMinimumCoinsBasic() { + int amount = 12; + int[] coins = {2, 4, 5}; + + assertEquals(3, CoinChange.minimumCoins(coins, amount)); + } + + @Test + void testMinimumCoinsNoCoins() { + int amount = 12; + int[] coins = {}; + + assertEquals(Integer.MAX_VALUE, CoinChange.minimumCoins(coins, amount)); + } + + @Test + void testMinimumCoinsNoAmount() { + int amount = 0; + int[] coins = {2, 4, 5}; + + assertEquals(0, CoinChange.minimumCoins(coins, amount)); + } + + @Test + void testMinimumCoinsImpossibleAmount() { + int amount = 3; + int[] coins = {2, 4, 5}; + + assertEquals(Integer.MAX_VALUE, CoinChange.minimumCoins(coins, amount)); + } + + @Test + void testMinimumCoinsExactAmount() { + int amount = 10; + int[] coins = {1, 5, 10}; + + assertEquals(1, CoinChange.minimumCoins(coins, amount)); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/CountFriendsPairingTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/CountFriendsPairingTest.java new file mode 100644 index 000000000000..765daba5f69f --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/CountFriendsPairingTest.java @@ -0,0 +1,50 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class CountFriendsPairingTest { + + @Test + void testSmallCase() { + int n = 5; + int[] expectedGolombSequence = {1, 2, 2, 3, 3}; + + assertTrue(CountFriendsPairing.countFriendsPairing(n, expectedGolombSequence)); + } + + @Test + void testMismatchSequence() { + int n = 5; + int[] wrongSequence = {1, 2, 2, 2, 3}; // An incorrect sequence + + assertFalse(CountFriendsPairing.countFriendsPairing(n, wrongSequence)); + } + + @Test + void testLargerCase() { + int n = 10; + int[] expectedGolombSequence = {1, 2, 2, 3, 3, 4, 4, 4, 5, 5}; + + assertTrue(CountFriendsPairing.countFriendsPairing(n, expectedGolombSequence)); + } + + @Test + void testEdgeCaseSingleElement() { + int n = 1; + int[] expectedGolombSequence = {1}; + + assertTrue(CountFriendsPairing.countFriendsPairing(n, expectedGolombSequence)); + } + + @Test + void testEmptySequence() { + int n = 0; + int[] emptySequence = {}; + + // Test the case where n is 0 (should handle this gracefully) + assertTrue(CountFriendsPairing.countFriendsPairing(n, emptySequence)); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/DPTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/DPTest.java new file mode 100644 index 000000000000..e3bea67fe269 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/DPTest.java @@ -0,0 +1,56 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class DPTest { + + @Test + void testSumLessThanMinimumFaceValue() { + // When the sum is less than the minimum possible face value + // There are 0 ways to achieve the sum + assertEquals(0, DP.findWays(4, 2, 1)); // 4 faces, 2 dice, sum = 1 + } + + @Test + void testTwoDiceWithSumEqualToTwo() { + // When there are 2 dice and the sum is equal to the number of dice + // The only way is to have both dice showing 1 + assertEquals(1, DP.findWays(2, 2, 2)); // 2 faces, 2 dice, sum = 2 + } + + @Test + void testTwoDiceWithSumThree() { + // When there are 2 dice and the sum is equal to 3 + // Possible combinations are (1,2) and (2,1) + assertEquals(2, DP.findWays(2, 2, 3)); // 2 faces, 2 dice, sum = 3 + } + + @Test + void testThreeDiceWithSumEight() { + // Test for 3 dice, each having 6 faces + // Possible combinations to make sum of 8 + assertEquals(21, DP.findWays(6, 3, 8)); // 6 faces, 3 dice, sum = 8 + } + + @Test + void testTwoDiceWithSumFive() { + // Test for 2 dice, with 4 faces to make sum of 5 + // Possible combinations: (1,4), (2,3), (3,2), (4,1) + assertEquals(4, DP.findWays(4, 2, 5)); // 4 faces, 2 dice, sum = 5 + } + + @Test + void testThreeDiceWithSumFive() { + // Test for 3 dice, with 4 faces to make sum of 5 + // Possible combinations: (1,1,3), (1,2,2), (1,3,1), (2,1,2), (2,2,1), (3,1,1) + assertEquals(6, DP.findWays(4, 3, 5)); // 4 faces, 3 dice, sum = 5 + } + + @Test + void testEdgeCaseZeroSum() { + // Test for 0 sum with 0 dice + assertEquals(0, DP.findWays(4, 0, 0)); // 4 faces, 0 dice, sum = 0 + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/EditDistanceTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/EditDistanceTest.java index 267be9b056de..737e8d1d0918 100644 --- a/src/test/java/com/thealgorithms/dynamicprogramming/EditDistanceTest.java +++ b/src/test/java/com/thealgorithms/dynamicprogramming/EditDistanceTest.java @@ -1,7 +1,9 @@ package com.thealgorithms.dynamicprogramming; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -12,4 +14,91 @@ public class EditDistanceTest { void testMinDistance(String str1, String str2, int expected) { assertEquals(expected, EditDistance.minDistance(str1, str2)); } + + @Test + public void testEditDistanceBothEmptyStrings() { + assertEquals(0, EditDistance.editDistance("", "")); + } + + @Test + public void testEditDistanceOneEmptyString() { + assertEquals(5, EditDistance.editDistance("", "hello")); + assertEquals(7, EditDistance.editDistance("worldly", "")); + } + + @Test + public void testEditDistanceOneEmptyStringMemoization() { + int[][] storage = new int[1][6]; + assertAll("String assertions", + () + -> assertEquals(5, EditDistance.editDistance("", "hello", storage)), + () -> assertEquals(0, storage[0][0]), () -> assertEquals(0, storage[0][1]), () -> assertEquals(0, storage[0][2]), () -> assertEquals(0, storage[0][3]), () -> assertEquals(0, storage[0][4]), () -> assertEquals(5, storage[0][5])); + } + + @Test + public void testEditDistanceEqualStrings() { + assertEquals(0, EditDistance.editDistance("test", "test")); + assertEquals(0, EditDistance.editDistance("abc", "abc")); + } + + @Test + public void testEditDistanceEqualStringsMemoization() { + int[][] storage = new int[4][4]; + assertAll("String assertions", + () + -> assertEquals(0, EditDistance.editDistance("abc", "abc", storage)), + () + -> assertEquals(0, storage[0][0]), + () + -> assertEquals(0, storage[0][1]), + () + -> assertEquals(0, storage[0][2]), + () + -> assertEquals(0, storage[0][3]), + () + -> assertEquals(0, storage[1][0]), + () + -> assertEquals(0, storage[1][1]), + () + -> assertEquals(0, storage[1][2]), + () + -> assertEquals(0, storage[1][3]), + () + -> assertEquals(0, storage[2][0]), + () -> assertEquals(0, storage[2][1]), () -> assertEquals(0, storage[2][2]), () -> assertEquals(0, storage[2][3]), () -> assertEquals(0, storage[3][0]), () -> assertEquals(0, storage[3][1]), () -> assertEquals(0, storage[3][2]), () -> assertEquals(0, storage[3][3])); + } + + @Test + public void testEditDistanceOneCharacterDifference() { + assertEquals(1, EditDistance.editDistance("cat", "bat")); + assertEquals(1, EditDistance.editDistance("cat", "cats")); + assertEquals(1, EditDistance.editDistance("cats", "cat")); + } + + @Test + public void testEditDistanceOneCharacterDifferenceMemoization() { + int[][] storage = new int[3][3]; + assertAll("All assertions", + () + -> assertEquals(1, EditDistance.editDistance("at", "it", storage)), + () + -> assertEquals(0, storage[0][0]), + () + -> assertEquals(1, storage[0][1]), + () -> assertEquals(2, storage[0][2]), () -> assertEquals(1, storage[1][0]), () -> assertEquals(0, storage[1][1]), () -> assertEquals(1, storage[1][2]), () -> assertEquals(2, storage[2][0]), () -> assertEquals(1, storage[2][1]), () -> assertEquals(1, storage[2][2])); + } + + @Test + public void testEditDistanceGeneralCases() { + assertEquals(3, EditDistance.editDistance("kitten", "sitting")); + assertEquals(2, EditDistance.editDistance("flaw", "lawn")); + assertEquals(5, EditDistance.editDistance("intention", "execution")); + } + + @Test + public void testEditDistanceGeneralCasesMemoization() { + int[][] storage = new int[7][8]; + assertEquals(3, EditDistance.editDistance("kitten", "sitting", storage)); + assertAll("All assertions", () -> assertEquals(0, storage[0][0]), () -> assertEquals(3, storage[6][7])); + } } diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/FibonacciTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/FibonacciTest.java new file mode 100644 index 000000000000..166e20c3083f --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/FibonacciTest.java @@ -0,0 +1,89 @@ +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.BeforeEach; +import org.junit.jupiter.api.Test; + +public class FibonacciTest { + + @BeforeEach + void setUp() { + // Clear the cache before each test to avoid interference + Fibonacci.CACHE.clear(); + } + + @Test + void testFibMemo() { + // Test memoization method + assertEquals(0, Fibonacci.fibMemo(0)); + assertEquals(1, Fibonacci.fibMemo(1)); + assertEquals(1, Fibonacci.fibMemo(2)); + assertEquals(2, Fibonacci.fibMemo(3)); + assertEquals(3, Fibonacci.fibMemo(4)); + assertEquals(5, Fibonacci.fibMemo(5)); + assertEquals(8, Fibonacci.fibMemo(6)); + assertEquals(13, Fibonacci.fibMemo(7)); + assertEquals(21, Fibonacci.fibMemo(8)); + assertEquals(34, Fibonacci.fibMemo(9)); + assertEquals(55, Fibonacci.fibMemo(10)); + } + + @Test + void testFibBotUp() { + // Test bottom-up method + assertEquals(0, Fibonacci.fibBotUp(0)); + assertEquals(1, Fibonacci.fibBotUp(1)); + assertEquals(1, Fibonacci.fibBotUp(2)); + assertEquals(2, Fibonacci.fibBotUp(3)); + assertEquals(3, Fibonacci.fibBotUp(4)); + assertEquals(5, Fibonacci.fibBotUp(5)); + assertEquals(8, Fibonacci.fibBotUp(6)); + assertEquals(13, Fibonacci.fibBotUp(7)); + assertEquals(21, Fibonacci.fibBotUp(8)); + assertEquals(34, Fibonacci.fibBotUp(9)); + assertEquals(55, Fibonacci.fibBotUp(10)); + } + + @Test + void testFibOptimized() { + // Test optimized Fibonacci method + assertEquals(0, Fibonacci.fibOptimized(0)); + assertEquals(1, Fibonacci.fibOptimized(1)); + assertEquals(1, Fibonacci.fibOptimized(2)); + assertEquals(2, Fibonacci.fibOptimized(3)); + assertEquals(3, Fibonacci.fibOptimized(4)); + assertEquals(5, Fibonacci.fibOptimized(5)); + assertEquals(8, Fibonacci.fibOptimized(6)); + assertEquals(13, Fibonacci.fibOptimized(7)); + assertEquals(21, Fibonacci.fibOptimized(8)); + assertEquals(34, Fibonacci.fibOptimized(9)); + assertEquals(55, Fibonacci.fibOptimized(10)); + } + + @Test + void testFibBinet() { + // Test Binet's formula method + assertEquals(0, Fibonacci.fibBinet(0)); + assertEquals(1, Fibonacci.fibBinet(1)); + assertEquals(1, Fibonacci.fibBinet(2)); + assertEquals(2, Fibonacci.fibBinet(3)); + assertEquals(3, Fibonacci.fibBinet(4)); + assertEquals(5, Fibonacci.fibBinet(5)); + assertEquals(8, Fibonacci.fibBinet(6)); + assertEquals(13, Fibonacci.fibBinet(7)); + assertEquals(21, Fibonacci.fibBinet(8)); + assertEquals(34, Fibonacci.fibBinet(9)); + assertEquals(55, Fibonacci.fibBinet(10)); + } + + @Test + void testNegativeInput() { + // Test negative input; Fibonacci is not defined for negative numbers + assertThrows(IllegalArgumentException.class, () -> { Fibonacci.fibMemo(-1); }); + assertThrows(IllegalArgumentException.class, () -> { Fibonacci.fibBotUp(-1); }); + assertThrows(IllegalArgumentException.class, () -> { Fibonacci.fibOptimized(-1); }); + assertThrows(IllegalArgumentException.class, () -> { Fibonacci.fibBinet(-1); }); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/KadaneAlgorithmTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/KadaneAlgorithmTest.java new file mode 100644 index 000000000000..e26606ef98a9 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/KadaneAlgorithmTest.java @@ -0,0 +1,61 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class KadaneAlgorithmTest { + + @Test + void testMaxSumWithPositiveValues() { + // Test with all positive numbers + int[] input = {89, 56, 98, 123, 26, 75, 12, 40, 39, 68, 91}; + int expectedMaxSum = 89 + 56 + 98 + 123 + 26 + 75 + 12 + 40 + 39 + 68 + 91; // sum of all elements + assertTrue(KadaneAlgorithm.maxSum(input, expectedMaxSum)); + } + + @Test + void testMaxSumWithMixedValues() { + // Test with mixed positive and negative numbers + int[] input = {1, -2, 3, 4, -1, 2, 1, -5, 4}; + int expectedMaxSum = 3 + 4 + -1 + 2 + 1; // max subarray is [3, 4, -1, 2, 1] + assertTrue(KadaneAlgorithm.maxSum(input, expectedMaxSum)); + } + + @Test + void testMaxSumWithAllNegativeValues() { + // Test with all negative numbers + int[] input = {-2, -3, -1, -4}; + int expectedMaxSum = -1; // max subarray is the least negative number + assertTrue(KadaneAlgorithm.maxSum(input, expectedMaxSum)); + } + + @Test + void testMaxSumWithSingleElement() { + // Test with a single positive element + int[] input = {10}; + int expectedMaxSum = 10; // max subarray is the single element + assertTrue(KadaneAlgorithm.maxSum(input, expectedMaxSum)); + + // Test with a single negative element + input = new int[] {-10}; + expectedMaxSum = -10; // max subarray is the single element + assertTrue(KadaneAlgorithm.maxSum(input, expectedMaxSum)); + } + + @Test + void testMaxSumWithZero() { + // Test with zeros in the array + int[] input = {0, -1, 2, -2, 0, 3}; + int expectedMaxSum = 3; // max subarray is [2, -2, 0, 3] + assertTrue(KadaneAlgorithm.maxSum(input, expectedMaxSum)); + } + + @Test + void testMaxSumWithEmptyArray() { + // Test with an empty array; should ideally throw an exception or return false + int[] input = {}; + assertThrows(ArrayIndexOutOfBoundsException.class, () -> { KadaneAlgorithm.maxSum(input, 0); }); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackMemoizationTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackMemoizationTest.java index d220a2bb512e..3545eb2667ed 100644 --- a/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackMemoizationTest.java +++ b/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackMemoizationTest.java @@ -31,4 +31,19 @@ void test3() { int capacity = 50; assertEquals(220, knapsackMemoization.knapSack(capacity, weight, value, weight.length)); } + + @Test + void test4() { + int[] weight = {1, 2, 3}; + int[] value = {10, 20, 30}; + int capacity = 0; + assertEquals(0, knapsackMemoization.knapSack(capacity, weight, value, weight.length)); + } + @Test + void test5() { + int[] weight = {1, 2, 3, 8}; + int[] value = {10, 20, 30, 40}; + int capacity = 50; + assertEquals(100, knapsackMemoization.knapSack(capacity, weight, value, weight.length)); + } } diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java new file mode 100644 index 000000000000..dc87d6751460 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java @@ -0,0 +1,22 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class LongestIncreasingSubsequenceNLogNTest { + + private static Stream provideTestCases() { + return Stream.of(Arguments.of(new int[] {10, 9, 2, 5, 3, 7, 101, 18}, 4), Arguments.of(new int[] {0, 1, 0, 3, 2, 3}, 4), Arguments.of(new int[] {7, 7, 7, 7, 7}, 1), Arguments.of(new int[] {1, 3, 5, 4, 7}, 4), Arguments.of(new int[] {}, 0), Arguments.of(new int[] {10}, 1), + Arguments.of(new int[] {3, 10, 2, 1, 20}, 3), Arguments.of(new int[] {50, 3, 10, 7, 40, 80}, 4)); + } + + @ParameterizedTest + @MethodSource("provideTestCases") + public void testLengthOfLIS(int[] input, int expected) { + assertEquals(expected, LongestIncreasingSubsequenceNLogN.lengthOfLIS(input)); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/MatrixChainMultiplicationTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/MatrixChainMultiplicationTest.java new file mode 100644 index 000000000000..2bee0ca52918 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/MatrixChainMultiplicationTest.java @@ -0,0 +1,54 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import org.junit.jupiter.api.Test; + +class MatrixChainMultiplicationTest { + + @Test + void testMatrixCreation() { + MatrixChainMultiplication.Matrix matrix1 = new MatrixChainMultiplication.Matrix(1, 10, 20); + MatrixChainMultiplication.Matrix matrix2 = new MatrixChainMultiplication.Matrix(2, 20, 30); + + assertEquals(1, matrix1.count()); + assertEquals(10, matrix1.col()); + assertEquals(20, matrix1.row()); + + assertEquals(2, matrix2.count()); + assertEquals(20, matrix2.col()); + assertEquals(30, matrix2.row()); + } + + @Test + void testMatrixChainOrder() { + // Create a list of matrices to be multiplied + ArrayList matrices = new ArrayList<>(); + matrices.add(new MatrixChainMultiplication.Matrix(1, 10, 20)); // A(1) = 10 x 20 + matrices.add(new MatrixChainMultiplication.Matrix(2, 20, 30)); // A(2) = 20 x 30 + + // Calculate matrix chain order + MatrixChainMultiplication.Result result = MatrixChainMultiplication.calculateMatrixChainOrder(matrices); + + // Expected cost of multiplying A(1) and A(2) + int expectedCost = 6000; // The expected optimal cost of multiplying A(1)(10x20) and A(2)(20x30) + int actualCost = result.getM()[1][2]; + + assertEquals(expectedCost, actualCost); + } + + @Test + void testOptimalParentheses() { + // Create a list of matrices to be multiplied + ArrayList matrices = new ArrayList<>(); + matrices.add(new MatrixChainMultiplication.Matrix(1, 10, 20)); // A(1) = 10 x 20 + matrices.add(new MatrixChainMultiplication.Matrix(2, 20, 30)); // A(2) = 20 x 30 + + // Calculate matrix chain order + MatrixChainMultiplication.Result result = MatrixChainMultiplication.calculateMatrixChainOrder(matrices); + + // Check the optimal split for parentheses + assertEquals(1, result.getS()[1][2]); // s[1][2] should point to the optimal split + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/MatrixChainRecursiveTopDownMemoisationTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/MatrixChainRecursiveTopDownMemoisationTest.java new file mode 100644 index 000000000000..f8270f6d50b5 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/MatrixChainRecursiveTopDownMemoisationTest.java @@ -0,0 +1,68 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class MatrixChainRecursiveTopDownMemoisationTest { + + /** + * Test case for four matrices with dimensions 1x2, 2x3, 3x4, and 4x5. + * The expected minimum number of multiplications is 38. + */ + @Test + void testFourMatrices() { + int[] dimensions = {1, 2, 3, 4, 5}; + int expected = 38; + int actual = MatrixChainRecursiveTopDownMemoisation.memoizedMatrixChain(dimensions); + assertEquals(expected, actual, "The minimum number of multiplications should be 38."); + } + + /** + * Test case for three matrices with dimensions 10x20, 20x30, and 30x40. + * The expected minimum number of multiplications is 6000. + */ + @Test + void testThreeMatrices() { + int[] dimensions = {10, 20, 30, 40}; + int expected = 18000; + int actual = MatrixChainRecursiveTopDownMemoisation.memoizedMatrixChain(dimensions); + assertEquals(expected, actual, "The minimum number of multiplications should be 18000."); + } + + /** + * Test case for two matrices with dimensions 5x10 and 10x20. + * The expected minimum number of multiplications is 1000. + */ + @Test + void testTwoMatrices() { + int[] dimensions = {5, 10, 20}; + int expected = 1000; + int actual = MatrixChainRecursiveTopDownMemoisation.memoizedMatrixChain(dimensions); + assertEquals(expected, actual, "The minimum number of multiplications should be 1000."); + } + + /** + * Test case for a single matrix. + * The expected minimum number of multiplications is 0, as there are no multiplications needed. + */ + @Test + void testSingleMatrix() { + int[] dimensions = {10, 20}; // Single matrix dimensions + int expected = 0; + int actual = MatrixChainRecursiveTopDownMemoisation.memoizedMatrixChain(dimensions); + assertEquals(expected, actual, "The minimum number of multiplications should be 0."); + } + + /** + * Test case for matrices with varying dimensions. + * The expected minimum number of multiplications is calculated based on the dimensions provided. + */ + @Test + void testVaryingDimensions() { + int[] dimensions = {2, 3, 4, 5, 6}; // Dimensions for 4 matrices + int expected = 124; // Expected value needs to be calculated based on the problem + int actual = MatrixChainRecursiveTopDownMemoisation.memoizedMatrixChain(dimensions); + assertEquals(expected, actual, "The minimum number of multiplications should be 124."); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/MaximumSumOfNonAdjacentElementsTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/MaximumSumOfNonAdjacentElementsTest.java new file mode 100644 index 000000000000..3f312d86462e --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/MaximumSumOfNonAdjacentElementsTest.java @@ -0,0 +1,52 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class MaximumSumOfNonAdjacentElementsTest { + + // Tests for Approach1 + @Test + public void testGetMaxSumApproach1WithEmptyArray() { + assertEquals(0, MaximumSumOfNonAdjacentElements.getMaxSumApproach1(new int[] {})); // Empty array + } + + @Test + public void testGetMaxSumApproach1WithSingleElement() { + assertEquals(1, MaximumSumOfNonAdjacentElements.getMaxSumApproach1(new int[] {1})); // Single element + } + + @Test + public void testGetMaxSumApproach1WithTwoElementsTakeMax() { + assertEquals(2, MaximumSumOfNonAdjacentElements.getMaxSumApproach1(new int[] {1, 2})); // Take max of both + } + + @Test + public void testGetMaxSumApproach1WithMultipleElements() { + assertEquals(15, MaximumSumOfNonAdjacentElements.getMaxSumApproach1(new int[] {3, 2, 5, 10, 7})); // 3 + 7 + 5 + assertEquals(10, MaximumSumOfNonAdjacentElements.getMaxSumApproach1(new int[] {5, 1, 1, 5})); // 5 + 5 + } + + // Tests for Approach2 + @Test + public void testGetMaxSumApproach2WithEmptyArray() { + assertEquals(0, MaximumSumOfNonAdjacentElements.getMaxSumApproach2(new int[] {})); // Empty array + } + + @Test + public void testGetMaxSumApproach2WithSingleElement() { + assertEquals(1, MaximumSumOfNonAdjacentElements.getMaxSumApproach2(new int[] {1})); // Single element + } + + @Test + public void testGetMaxSumApproach2WithTwoElementsTakeMax() { + assertEquals(2, MaximumSumOfNonAdjacentElements.getMaxSumApproach2(new int[] {1, 2})); // Take max of both + } + + @Test + public void testGetMaxSumApproach2WithMultipleElements() { + assertEquals(15, MaximumSumOfNonAdjacentElements.getMaxSumApproach2(new int[] {3, 2, 5, 10, 7})); // 3 + 7 + 5 + assertEquals(10, MaximumSumOfNonAdjacentElements.getMaxSumApproach2(new int[] {5, 1, 1, 5})); // 5 + 5 + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/NewManShanksPrimeTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/NewManShanksPrimeTest.java new file mode 100644 index 000000000000..a16ad67d6470 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/NewManShanksPrimeTest.java @@ -0,0 +1,80 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the NewManShanksPrime class. + * This test class verifies the correctness of the nthManShanksPrime method + * for various input cases. + */ +class NewManShanksPrimeTest { + + /** + * Test case for the 1st New Man Shanks prime. + * The expected answer is 1. + */ + @Test + void testNthManShanksPrime1() { + int n = 1; + int expectedAnswer = 1; + assertTrue(NewManShanksPrime.nthManShanksPrime(n, expectedAnswer), "The 1st New Man Shanks prime should be 1."); + } + + /** + * Test case for the 2nd New Man Shanks prime. + * The expected answer is 3. + */ + @Test + void testNthManShanksPrime2() { + int n = 2; + int expectedAnswer = 3; + assertTrue(NewManShanksPrime.nthManShanksPrime(n, expectedAnswer), "The 2nd New Man Shanks prime should be 3."); + } + + /** + * Test case for the 3rd New Man Shanks prime. + * The expected answer is 7. + */ + @Test + void testNthManShanksPrime3() { + int n = 3; + int expectedAnswer = 7; + assertTrue(NewManShanksPrime.nthManShanksPrime(n, expectedAnswer), "The 3rd New Man Shanks prime should be 7."); + } + + /** + * Test case for the 4th New Man Shanks prime. + * The expected answer is 17. + */ + @Test + void testNthManShanksPrime4() { + int n = 4; + int expectedAnswer = 17; + assertTrue(NewManShanksPrime.nthManShanksPrime(n, expectedAnswer), "The 4th New Man Shanks prime should be 17."); + } + + /** + * Test case for the 5th New Man Shanks prime. + * The expected answer is 41. + */ + @Test + void testNthManShanksPrime5() { + int n = 5; + int expectedAnswer = 41; + assertTrue(NewManShanksPrime.nthManShanksPrime(n, expectedAnswer), "The 5th New Man Shanks prime should be 41."); + } + + /** + * Test case with an incorrect expected answer. + * For n = 2, the expected answer is 3. + */ + @Test + void testNthManShanksPrimeIncorrectAnswer() { + int n = 2; + int expectedAnswer = 4; // Incorrect expected value + assertFalse(NewManShanksPrime.nthManShanksPrime(n, expectedAnswer), "The 2nd New Man Shanks prime should not be 4."); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/RodCuttingTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/RodCuttingTest.java new file mode 100644 index 000000000000..9cf21fd836db --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/RodCuttingTest.java @@ -0,0 +1,102 @@ +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; + +/** + * Unit tests for the RodCutting class. + * This test class verifies the correctness of the cutRod method for various input cases. + */ +class RodCuttingTest { + + /** + * Test case for cutting a rod of length 1. + * The expected maximum obtainable value is the price of the piece of length 1. + */ + @Test + void testCutRodLength1() { + int[] prices = {1}; // Price for piece of length 1 + int length = 1; + int expectedValue = 1; + assertEquals(expectedValue, RodCutting.cutRod(prices, length), "The maximum obtainable value for a rod of length 1 should be 1."); + } + + /** + * Test case for cutting a rod of length 2. + * The expected maximum obtainable value is the best price combination for length 2. + */ + @Test + void testCutRodLength2() { + int[] prices = {1, 5}; // Prices for lengths 1 and 2 + int length = 2; + int expectedValue = 5; // Best value is to cut it into a single piece of length 2 + assertEquals(expectedValue, RodCutting.cutRod(prices, length), "The maximum obtainable value for a rod of length 2 should be 5."); + } + + /** + * Test case for cutting a rod of length 3. + * The expected maximum obtainable value is the best price combination for length 3. + */ + @Test + void testCutRodLength3() { + int[] prices = {1, 5, 8}; // Prices for lengths 1, 2, and 3 + int length = 3; + int expectedValue = 8; // Best value is to cut it into a single piece of length 3 + assertEquals(expectedValue, RodCutting.cutRod(prices, length), "The maximum obtainable value for a rod of length 3 should be 8."); + } + + /** + * Test case for cutting a rod of length 4. + * The expected maximum obtainable value is the best price combination for length 4. + */ + @Test + void testCutRodLength4() { + int[] prices = {1, 5, 8, 9}; // Prices for lengths 1, 2, 3, and 4 + int length = 4; + int expectedValue = 10; // Best value is to cut it into two pieces of length 2 + assertEquals(expectedValue, RodCutting.cutRod(prices, length), "The maximum obtainable value for a rod of length 4 should be 10."); + } + + /** + * Test case for cutting a rod of length 5. + * The expected maximum obtainable value is the best price combination for length 5. + */ + @Test + void testCutRodLength5() { + int[] prices = {1, 5, 8, 9, 10}; // Prices for lengths 1, 2, 3, 4, and 5 + int length = 5; + int expectedValue = 13; // Best value is to cut it into pieces of lengths 2 and 3 + assertEquals(expectedValue, RodCutting.cutRod(prices, length), "The maximum obtainable value for a rod of length 5 should be 13."); + } + + /** + * Test case for cutting a rod of length 0. + * The expected maximum obtainable value should be 0 since the rod has no length. + */ + @Test + void testCutRodLength0() { + int[] prices = {1, 5, 8, 9, 10}; // Prices are irrelevant for length 0 + int length = 0; + int expectedValue = 0; // No value obtainable from a rod of length 0 + assertEquals(expectedValue, RodCutting.cutRod(prices, length), "The maximum obtainable value for a rod of length 0 should be 0."); + } + + /** + * Test case for an empty prices array. + * The expected maximum obtainable value should still be 0 for any length. + */ + @Test + void testCutRodEmptyPrices() { + int[] prices = {}; + int length = 5; + assertThrows(IllegalArgumentException.class, () -> RodCutting.cutRod(prices, length), "An empty prices array should throw an IllegalArgumentException."); + } + @Test + void testCutRodNegativeLength() { + int[] prices = {1, 5, 8, 9, 10}; // Prices are irrelevant for negative length + int length = -1; + assertThrows(IllegalArgumentException.class, () -> RodCutting.cutRod(prices, length), "A negative rod length should throw an IllegalArgumentException."); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimizedTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimizedTest.java new file mode 100644 index 000000000000..3a965f4e68b8 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimizedTest.java @@ -0,0 +1,18 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class SubsetSumSpaceOptimizedTest { + + @Test + void basicCheck() { + assertTrue(SubsetSumSpaceOptimized.isSubsetSum(new int[] {7, 3, 2, 5, 8}, 14)); + assertTrue(SubsetSumSpaceOptimized.isSubsetSum(new int[] {4, 3, 2, 1}, 5)); + assertTrue(SubsetSumSpaceOptimized.isSubsetSum(new int[] {1, 7, 2, 9, 10}, 13)); + assertFalse(SubsetSumSpaceOptimized.isSubsetSum(new int[] {1, 2, 7, 10, 9}, 14)); + assertFalse(SubsetSumSpaceOptimized.isSubsetSum(new int[] {2, 15, 1, 6, 7}, 4)); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/TreeMatchingTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/TreeMatchingTest.java new file mode 100644 index 000000000000..d5418770a5d1 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/TreeMatchingTest.java @@ -0,0 +1,120 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.thealgorithms.datastructures.graphs.UndirectedAdjacencyListGraph; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class TreeMatchingTest { + UndirectedAdjacencyListGraph graph; + + @BeforeEach + void setUp() { + graph = new UndirectedAdjacencyListGraph(); + for (int i = 0; i < 14; i++) { + graph.addNode(); + } + } + + @Test + void testMaxMatchingForGeneralTree() { + graph.addEdge(0, 1, 20); + graph.addEdge(0, 2, 30); + graph.addEdge(1, 3, 40); + graph.addEdge(1, 4, 10); + graph.addEdge(2, 5, 20); + graph.addEdge(3, 6, 30); + graph.addEdge(3, 7, 30); + graph.addEdge(5, 8, 40); + graph.addEdge(5, 9, 10); + + TreeMatching treeMatching = new TreeMatching(graph); + assertEquals(110, treeMatching.getMaxMatching(0, -1)); + } + + @Test + void testMaxMatchingForBalancedTree() { + graph.addEdge(0, 1, 20); + graph.addEdge(0, 2, 30); + graph.addEdge(0, 3, 40); + graph.addEdge(1, 4, 10); + graph.addEdge(1, 5, 20); + graph.addEdge(2, 6, 20); + graph.addEdge(3, 7, 30); + graph.addEdge(5, 8, 10); + graph.addEdge(5, 9, 20); + graph.addEdge(7, 10, 10); + graph.addEdge(7, 11, 10); + graph.addEdge(7, 12, 5); + TreeMatching treeMatching = new TreeMatching(graph); + assertEquals(100, treeMatching.getMaxMatching(0, -1)); + } + + @Test + void testMaxMatchingForTreeWithVariedEdgeWeights() { + graph.addEdge(0, 1, 20); + graph.addEdge(0, 2, 30); + graph.addEdge(0, 3, 40); + graph.addEdge(0, 4, 50); + graph.addEdge(1, 5, 20); + graph.addEdge(2, 6, 20); + graph.addEdge(3, 7, 30); + graph.addEdge(5, 8, 10); + graph.addEdge(5, 9, 20); + graph.addEdge(7, 10, 10); + graph.addEdge(4, 11, 50); + graph.addEdge(4, 12, 20); + TreeMatching treeMatching = new TreeMatching(graph); + assertEquals(140, treeMatching.getMaxMatching(0, -1)); + } + + @Test + void emptyTree() { + TreeMatching treeMatching = new TreeMatching(graph); + assertEquals(0, treeMatching.getMaxMatching(0, -1)); + } + + @Test + void testSingleNodeTree() { + UndirectedAdjacencyListGraph singleNodeGraph = new UndirectedAdjacencyListGraph(); + singleNodeGraph.addNode(); + + TreeMatching treeMatching = new TreeMatching(singleNodeGraph); + assertEquals(0, treeMatching.getMaxMatching(0, -1)); + } + + @Test + void testLinearTree() { + graph.addEdge(0, 1, 10); + graph.addEdge(1, 2, 20); + graph.addEdge(2, 3, 30); + graph.addEdge(3, 4, 40); + + TreeMatching treeMatching = new TreeMatching(graph); + assertEquals(60, treeMatching.getMaxMatching(0, -1)); + } + + @Test + void testStarShapedTree() { + graph.addEdge(0, 1, 15); + graph.addEdge(0, 2, 25); + graph.addEdge(0, 3, 35); + graph.addEdge(0, 4, 45); + + TreeMatching treeMatching = new TreeMatching(graph); + assertEquals(45, treeMatching.getMaxMatching(0, -1)); + } + + @Test + void testUnbalancedTree() { + graph.addEdge(0, 1, 10); + graph.addEdge(0, 2, 20); + graph.addEdge(1, 3, 30); + graph.addEdge(2, 4, 40); + graph.addEdge(4, 5, 50); + + TreeMatching treeMatching = new TreeMatching(graph); + assertEquals(100, treeMatching.getMaxMatching(0, -1)); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCountTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCountTest.java new file mode 100755 index 000000000000..049804f58b5a --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCountTest.java @@ -0,0 +1,15 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +public class UniqueSubsequencesCountTest { + + @ParameterizedTest + @CsvSource({"abc, 7", "abcdashgdhas, 3592", "a, 1", "'a b', 7", "a1b2, 15", "AaBb, 15", "abab, 11"}) + void subseqCountParameterizedTest(String input, int expected) { + assertEquals(expected, UniqueSubsequencesCount.countSubseq(input)); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java new file mode 100644 index 000000000000..fbcc2c6f3a83 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java @@ -0,0 +1,72 @@ +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; + +/** + * Unit tests for the WineProblem class. + * This test class verifies the correctness of the wine selling problem solutions. + */ +class WineProblemTest { + + /** + * Test for wpRecursion method. + */ + @Test + void testWpRecursion() { + int[] wines = {2, 3, 5, 1, 4}; // Prices of wines + int expectedProfit = 50; // The expected maximum profit + assertEquals(expectedProfit, WineProblem.wpRecursion(wines, 0, wines.length - 1), "The maximum profit using recursion should be 50."); + } + + /** + * Test for wptd method (Top-Down DP with Memoization). + */ + @Test + void testWptd() { + int[] wines = {2, 3, 5, 1, 4}; // Prices of wines + int expectedProfit = 50; // The expected maximum profit + assertEquals(expectedProfit, WineProblem.wptd(wines, 0, wines.length - 1, new int[wines.length][wines.length]), "The maximum profit using top-down DP should be 50."); + } + + /** + * Test for wpbu method (Bottom-Up DP with Tabulation). + */ + @Test + void testWpbu() { + int[] wines = {2, 3, 5, 1, 4}; // Prices of wines + int expectedProfit = 50; // The expected maximum profit + assertEquals(expectedProfit, WineProblem.wpbu(wines), "The maximum profit using bottom-up DP should be 50."); + } + + /** + * Test with a single wine. + */ + @Test + void testSingleWine() { + int[] wines = {10}; // Only one wine + int expectedProfit = 10; // Selling the only wine at year 1 + assertEquals(expectedProfit, WineProblem.wpbu(wines), "The maximum profit for a single wine should be 10."); + } + + /** + * Test with multiple wines of the same price. + */ + @Test + void testSamePriceWines() { + int[] wines = {5, 5, 5}; // All wines have the same price + int expectedProfit = 30; // Profit is 5 * (1 + 2 + 3) + assertEquals(expectedProfit, WineProblem.wpbu(wines), "The maximum profit with same price wines should be 30."); + } + + /** + * Test with no wines. + */ + @Test + void testNoWines() { + int[] wines = {}; + assertThrows(IllegalArgumentException.class, () -> WineProblem.wpbu(wines), "The maximum profit for no wines should throw an IllegalArgumentException."); + } +} diff --git a/src/test/java/com/thealgorithms/geometry/BresenhamLineTest.java b/src/test/java/com/thealgorithms/geometry/BresenhamLineTest.java new file mode 100644 index 000000000000..9df308497ddf --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/BresenhamLineTest.java @@ -0,0 +1,57 @@ +package com.thealgorithms.geometry; + +import java.awt.Point; +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * The {@code BresenhamLineTest} class contains unit tests for the + * {@code BresenhamLine} class, specifically testing the + * {@code findLine} method. + * + *

This class uses parameterized tests to validate the output of + * Bresenham's line algorithm for various input points.

+ */ +class BresenhamLineTest { + + /** + * Provides test cases for the parameterized test. + * + *

Each test case includes starting coordinates, ending coordinates, + * and the expected collection of points that should be generated by the + * {@code findLine} method.

+ * + * @return a stream of arguments containing test cases + */ + static Stream linePointsProvider() { + return Stream.of(Arguments.of(0, 0, 5, 5, List.of(new Point(0, 0), new Point(1, 1), new Point(2, 2), new Point(3, 3), new Point(4, 4), new Point(5, 5))), Arguments.of(0, 0, 5, 0, List.of(new Point(0, 0), new Point(1, 0), new Point(2, 0), new Point(3, 0), new Point(4, 0), new Point(5, 0))), + Arguments.of(0, 0, 0, 5, List.of(new Point(0, 0), new Point(0, 1), new Point(0, 2), new Point(0, 3), new Point(0, 4), new Point(0, 5))), Arguments.of(-2, -2, -5, -5, List.of(new Point(-2, -2), new Point(-3, -3), new Point(-4, -4), new Point(-5, -5))), + Arguments.of(-1, -1, 2, 2, List.of(new Point(-1, -1), new Point(0, 0), new Point(1, 1), new Point(2, 2))), Arguments.of(2, -1, -1, -4, List.of(new Point(2, -1), new Point(1, -2), new Point(0, -3), new Point(-1, -4)))); + } + + /** + * Tests the {@code findLine} method of the {@code BresenhamLine} class. + * + *

This parameterized test runs multiple times with different sets of + * starting and ending coordinates to validate that the generated points + * match the expected output.

+ * + * @param x0 the x-coordinate of the starting point + * @param y0 the y-coordinate of the starting point + * @param x1 the x-coordinate of the ending point + * @param y1 the y-coordinate of the ending point + * @param expected a collection of expected points that should form a line + */ + @ParameterizedTest + @MethodSource("linePointsProvider") + void testFindLine(int x0, int y0, int x1, int y1, Collection expected) { + List actual = BresenhamLine.findLine(x0, y0, x1, y1); + Assertions.assertEquals(expected.size(), actual.size(), "The size of the points list should match."); + Assertions.assertTrue(expected.containsAll(actual) && actual.containsAll(expected), "The points generated should match the expected points."); + } +} diff --git a/src/test/java/com/thealgorithms/geometry/ConvexHullTest.java b/src/test/java/com/thealgorithms/geometry/ConvexHullTest.java new file mode 100644 index 000000000000..e3e32e43c6de --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/ConvexHullTest.java @@ -0,0 +1,40 @@ +package com.thealgorithms.geometry; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class ConvexHullTest { + + @Test + void testConvexHullBruteForce() { + List points = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 1)); + List expected = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 1)); + assertEquals(expected, ConvexHull.convexHullBruteForce(points)); + + points = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 0)); + expected = Arrays.asList(new Point(0, 0), new Point(10, 0)); + assertEquals(expected, ConvexHull.convexHullBruteForce(points)); + + points = Arrays.asList(new Point(0, 3), new Point(2, 2), new Point(1, 1), new Point(2, 1), new Point(3, 0), new Point(0, 0), new Point(3, 3), new Point(2, -1), new Point(2, -4), new Point(1, -3)); + expected = Arrays.asList(new Point(2, -4), new Point(1, -3), new Point(0, 0), new Point(3, 0), new Point(0, 3), new Point(3, 3)); + assertEquals(expected, ConvexHull.convexHullBruteForce(points)); + } + + @Test + void testConvexHullRecursive() { + List points = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 1)); + List expected = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 1)); + assertEquals(expected, ConvexHull.convexHullRecursive(points)); + + points = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 0)); + expected = Arrays.asList(new Point(0, 0), new Point(10, 0)); + assertEquals(expected, ConvexHull.convexHullRecursive(points)); + + points = Arrays.asList(new Point(0, 3), new Point(2, 2), new Point(1, 1), new Point(2, 1), new Point(3, 0), new Point(0, 0), new Point(3, 3), new Point(2, -1), new Point(2, -4), new Point(1, -3)); + expected = Arrays.asList(new Point(2, -4), new Point(1, -3), new Point(0, 0), new Point(3, 0), new Point(0, 3), new Point(3, 3)); + assertEquals(expected, ConvexHull.convexHullRecursive(points)); + } +} diff --git a/src/test/java/com/thealgorithms/geometry/GrahamScanTest.java b/src/test/java/com/thealgorithms/geometry/GrahamScanTest.java index e59cd6b860cc..622273881a27 100644 --- a/src/test/java/com/thealgorithms/geometry/GrahamScanTest.java +++ b/src/test/java/com/thealgorithms/geometry/GrahamScanTest.java @@ -7,7 +7,7 @@ public class GrahamScanTest { @Test void testGrahamScan() { - GrahamScan.Point[] points = {new GrahamScan.Point(0, 3), new GrahamScan.Point(1, 1), new GrahamScan.Point(2, 2), new GrahamScan.Point(4, 4), new GrahamScan.Point(0, 0), new GrahamScan.Point(1, 2), new GrahamScan.Point(3, 1), new GrahamScan.Point(3, 3)}; + Point[] points = {new Point(0, 3), new Point(1, 1), new Point(2, 2), new Point(4, 4), new Point(0, 0), new Point(1, 2), new Point(3, 1), new Point(3, 3)}; String expectedResult = "[(0, 0), (3, 1), (4, 4), (0, 3)]"; GrahamScan graham = new GrahamScan(points); diff --git a/src/test/java/com/thealgorithms/geometry/MidpointCircleTest.java b/src/test/java/com/thealgorithms/geometry/MidpointCircleTest.java new file mode 100644 index 000000000000..24370cd43b25 --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/MidpointCircleTest.java @@ -0,0 +1,55 @@ +package com.thealgorithms.geometry; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +/** + * Test class for the {@code MidpointCircle} class + */ +class MidpointCircleTest { + + /** + * Parameterized test to check the generated points for different circles. + * The points are checked based on the expected center and radius. + * + * @param centerX The x-coordinate of the circle's center. + * @param centerY The y-coordinate of the circle's center. + * @param radius The radius of the circle. + */ + @ParameterizedTest + @CsvSource({ + "0, 0, 3", // Circle centered at (0, 0) with radius 3 + "10, 10, 2" // Circle centered at (10, 10) with radius 2 + }) + void + testGenerateCirclePoints(int centerX, int centerY, int radius) { + List points = MidpointCircle.generateCirclePoints(centerX, centerY, radius); + + // Ensure that all points satisfy the circle equation (x - centerX)^2 + (y - centerY)^2 = radius^2 + for (int[] point : points) { + int x = point[0]; + int y = point[1]; + + int dx = x - centerX; + int dy = y - centerY; + int distanceSquared = dx * dx + dy * dy; + + assertTrue(Math.abs(distanceSquared - radius * radius) <= 1, "Point (" + x + ", " + y + ") does not satisfy the circle equation."); + } + } + + /** + * Test to ensure the algorithm generates points for a zero-radius circle. + */ + @Test + void testZeroRadiusCircle() { + List points = MidpointCircle.generateCirclePoints(0, 0, 0); + + // A zero-radius circle should only have one point: (0, 0) + assertTrue(points.size() == 1 && points.get(0)[0] == 0 && points.get(0)[1] == 0, "Zero-radius circle did not generate the correct point."); + } +} diff --git a/src/test/java/com/thealgorithms/geometry/MidpointEllipseTest.java b/src/test/java/com/thealgorithms/geometry/MidpointEllipseTest.java new file mode 100644 index 000000000000..9d03909c60ca --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/MidpointEllipseTest.java @@ -0,0 +1,99 @@ +package com.thealgorithms.geometry; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * The {@code MidpointEllipseTest} class contains unit tests for the + * {@code MidpointEllipse} class, specifically testing the + * {@code drawEllipse} method. + * + *

This class uses parameterized tests to validate the output of + * Midpoint Ellipse algorithm for various input points.

+ */ +class MidpointEllipseTest { + + /** + * Provides test cases for the drawEllipse method. + * Each argument contains: centerX, centerY, a, b, and expected points. + * + * @return a stream of arguments for parameterized testing + */ + static Stream ellipseTestProvider() { + return Stream.of( + Arguments.of(0, 0, 5, 3, new int[][] {{0, 3}, {0, 3}, {0, -3}, {0, -3}, {1, 3}, {-1, 3}, {1, -3}, {-1, -3}, {2, 3}, {-2, 3}, {2, -3}, {-2, -3}, {3, 2}, {-3, 2}, {3, -2}, {-3, -2}, {4, 2}, {-4, 2}, {4, -2}, {-4, -2}, {5, 1}, {-5, 1}, {5, -1}, {-5, -1}, {5, 0}, {-5, 0}, {5, 0}, {-5, 0}}), + Arguments.of(0, 0, 0, 5, + new int[][] { + {0, -5}, {0, -4}, {0, -3}, {0, -2}, {0, -1}, {0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5} // Only vertical line points and center + }), + Arguments.of(0, 0, 5, 0, + new int[][] { + {-5, 0}, {-4, 0}, {-3, 0}, {-2, 0}, {-1, 0}, {0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0} // Only horizontal line points and center + }), + Arguments.of(0, 0, 0, 0, + new int[][] { + {0, 0} // Only center point + }), + Arguments.of(0, 0, 4, 4, + new int[][] { + {0, 4}, + {0, 4}, + {0, -4}, + {0, -4}, + {1, 4}, + {-1, 4}, + {1, -4}, + {-1, -4}, + {2, 3}, + {-2, 3}, + {2, -3}, + {-2, -3}, + {3, 3}, + {-3, 3}, + {3, -3}, + {-3, -3}, + {3, 2}, + {-3, 2}, + {3, -2}, + {-3, -2}, + {4, 1}, + {-4, 1}, + {4, -1}, + {-4, -1}, + {4, 0}, + {-4, 0}, + {4, 0}, + {-4, 0}, + })); + } + + /** + * Tests the drawEllipse method with various parameters. + * + * @param centerX the x-coordinate of the center of the ellipse + * @param centerY the y-coordinate of the center of the ellipse + * @param a the length of the semi-major axis + * @param b the length of the semi-minor axis + * @param expectedPoints the expected points forming the ellipse + */ + @ParameterizedTest + @MethodSource("ellipseTestProvider") + @DisplayName("Test drawing ellipses with various parameters") + void testDrawEllipse(int centerX, int centerY, int a, int b, int[][] expectedPoints) { + List points = MidpointEllipse.drawEllipse(centerX, centerY, a, b); + + // Validate the number of points and the specific points + assertEquals(expectedPoints.length, points.size(), "Number of points should match expected."); + + for (int i = 0; i < expectedPoints.length; i++) { + assertArrayEquals(expectedPoints[i], points.get(i), "Point mismatch at index " + i); + } + } +} diff --git a/src/test/java/com/thealgorithms/graph/ConstrainedShortestPathTest.java b/src/test/java/com/thealgorithms/graph/ConstrainedShortestPathTest.java new file mode 100644 index 000000000000..eccd359f2634 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/ConstrainedShortestPathTest.java @@ -0,0 +1,218 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.thealgorithms.graph.ConstrainedShortestPath.Graph; +import org.junit.jupiter.api.Test; + +public class ConstrainedShortestPathTest { + + /** + * Tests a simple linear graph to verify if the solver calculates the shortest path correctly. + * Expected: The minimal path cost from node 0 to node 2 should be 5 while not exceeding the resource limit. + */ + @Test + public void testSimpleGraph() { + Graph graph = new Graph(3); + graph.addEdge(0, 1, 2, 3); + graph.addEdge(1, 2, 3, 2); + + int maxResource = 5; + ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); + + assertEquals(5, solver.solve(0, 2)); + } + + /** + * Tests a graph where no valid path exists due to resource constraints. + * Expected: The solver should return -1, indicating no path is feasible. + */ + @Test + public void testNoPath() { + Graph graph = new Graph(3); + graph.addEdge(0, 1, 2, 6); + graph.addEdge(1, 2, 3, 6); + + int maxResource = 5; + ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); + + assertEquals(-1, solver.solve(0, 2)); + } + + /** + * Tests a graph with multiple paths between source and destination. + * Expected: The solver should choose the path with the minimal cost of 5, considering the resource limit. + */ + @Test + public void testMultiplePaths() { + Graph graph = new Graph(4); + graph.addEdge(0, 1, 1, 1); + graph.addEdge(1, 3, 5, 2); + graph.addEdge(0, 2, 2, 1); + graph.addEdge(2, 3, 3, 2); + + int maxResource = 3; + ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); + + assertEquals(5, solver.solve(0, 3)); + } + + /** + * Verifies that the solver allows a path exactly matching the resource limit. + * Expected: The path is valid with a total cost of 5. + */ + @Test + public void testExactResourceLimit() { + Graph graph = new Graph(3); + graph.addEdge(0, 1, 2, 3); + graph.addEdge(1, 2, 3, 2); + + int maxResource = 5; + ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); + + assertEquals(5, solver.solve(0, 2)); + } + + /** + * Tests a disconnected graph where the destination node cannot be reached. + * Expected: The solver should return -1, as the destination is unreachable. + */ + @Test + public void testDisconnectedGraph() { + Graph graph = new Graph(4); + graph.addEdge(0, 1, 2, 2); + graph.addEdge(2, 3, 3, 2); + + int maxResource = 5; + ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); + + assertEquals(-1, solver.solve(0, 3)); + } + + /** + * Tests a graph with cycles to ensure the solver does not fall into infinite loops and correctly calculates costs. + * Expected: The solver should compute the minimal path cost of 6. + */ + @Test + public void testGraphWithCycles() { + Graph graph = new Graph(4); + graph.addEdge(0, 1, 2, 1); + graph.addEdge(1, 2, 3, 1); + graph.addEdge(2, 0, 1, 1); + graph.addEdge(1, 3, 4, 2); + + int maxResource = 3; + ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); + + assertEquals(6, solver.solve(0, 3)); + } + + /** + * Tests the solver's performance and correctness on a large linear graph with 1000 nodes. + * Expected: The solver should efficiently calculate the shortest path with a cost of 999. + */ + @Test + public void testLargeGraphPerformance() { + int nodeCount = 1000; + Graph graph = new Graph(nodeCount); + for (int i = 0; i < nodeCount - 1; i++) { + graph.addEdge(i, i + 1, 1, 1); + } + + int maxResource = 1000; + ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); + + assertEquals(999, solver.solve(0, nodeCount - 1)); + } + + /** + * Tests a graph with isolated nodes to ensure the solver recognizes unreachable destinations. + * Expected: The solver should return -1 for unreachable nodes. + */ + @Test + public void testIsolatedNodes() { + Graph graph = new Graph(5); + graph.addEdge(0, 1, 2, 1); + graph.addEdge(1, 2, 3, 1); + + int maxResource = 5; + ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); + + assertEquals(-1, solver.solve(0, 3)); + } + + /** + * Tests a cyclic large graph with multiple overlapping paths. + * Expected: The solver should calculate the shortest path cost of 5. + */ + @Test + public void testCyclicLargeGraph() { + Graph graph = new Graph(10); + for (int i = 0; i < 9; i++) { + graph.addEdge(i, (i + 1) % 10, 1, 1); + } + graph.addEdge(0, 5, 5, 3); + + int maxResource = 10; + ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); + + assertEquals(5, solver.solve(0, 5)); + } + + /** + * Tests a large complex graph with multiple paths and varying resource constraints. + * Expected: The solver should identify the optimal path with a cost of 19 within the resource limit. + */ + @Test + public void testLargeComplexGraph() { + Graph graph = new Graph(10); + graph.addEdge(0, 1, 4, 2); + graph.addEdge(0, 2, 3, 3); + graph.addEdge(1, 3, 2, 1); + graph.addEdge(2, 3, 5, 2); + graph.addEdge(2, 4, 8, 4); + graph.addEdge(3, 5, 7, 3); + graph.addEdge(3, 6, 6, 2); + graph.addEdge(4, 6, 3, 2); + graph.addEdge(5, 7, 1, 1); + graph.addEdge(6, 7, 2, 2); + graph.addEdge(7, 8, 3, 1); + graph.addEdge(8, 9, 2, 1); + + int maxResource = 10; + ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); + + assertEquals(19, solver.solve(0, 9)); + } + + /** + * Edge case test where the graph has only one node and no edges. + * Expected: The minimal path cost is 0, as the start and destination are the same. + */ + @Test + public void testSingleNodeGraph() { + Graph graph = new Graph(1); + + int maxResource = 0; + ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); + + assertEquals(0, solver.solve(0, 0)); + } + + /** + * Tests a graph with multiple paths but a tight resource constraint. + * Expected: The solver should return -1 if no path can be found within the resource limit. + */ + @Test + public void testTightResourceConstraint() { + Graph graph = new Graph(4); + graph.addEdge(0, 1, 3, 4); + graph.addEdge(1, 2, 1, 2); + graph.addEdge(0, 2, 2, 2); + + int maxResource = 3; + ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); + + assertEquals(2, solver.solve(0, 2)); + } +} diff --git a/src/test/java/com/thealgorithms/graph/StronglyConnectedComponentOptimizedTest.java b/src/test/java/com/thealgorithms/graph/StronglyConnectedComponentOptimizedTest.java new file mode 100644 index 000000000000..9473a328c982 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/StronglyConnectedComponentOptimizedTest.java @@ -0,0 +1,80 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class StronglyConnectedComponentOptimizedTest { + + private StronglyConnectedComponentOptimized sccOptimized; + + @BeforeEach + public void setUp() { + sccOptimized = new StronglyConnectedComponentOptimized(); + } + + @Test + public void testSingleComponent() { + // Create a simple graph with 3 nodes, all forming one SCC + HashMap> adjList = new HashMap<>(); + adjList.put(0, new ArrayList<>(List.of(1))); + adjList.put(1, new ArrayList<>(List.of(2))); + adjList.put(2, new ArrayList<>(List.of(0))); + + int result = sccOptimized.getOutput(adjList, 3); + + // The entire graph is one strongly connected component + assertEquals(1, result, "There should be 1 strongly connected component."); + } + + @Test + public void testTwoComponents() { + // Create a graph with 4 nodes and two SCCs: {0, 1, 2} and {3} + HashMap> adjList = new HashMap<>(); + adjList.put(0, new ArrayList<>(List.of(1))); + adjList.put(1, new ArrayList<>(List.of(2))); + adjList.put(2, new ArrayList<>(List.of(0))); + adjList.put(3, new ArrayList<>()); + + int result = sccOptimized.getOutput(adjList, 4); + + // There are 2 SCCs: {0, 1, 2} and {3} + assertEquals(2, result, "There should be 2 strongly connected components."); + } + + @Test + public void testDisconnectedGraph() { + // Create a graph with 4 nodes that are all disconnected + HashMap> adjList = new HashMap<>(); + adjList.put(0, new ArrayList<>()); + adjList.put(1, new ArrayList<>()); + adjList.put(2, new ArrayList<>()); + adjList.put(3, new ArrayList<>()); + + int result = sccOptimized.getOutput(adjList, 4); + + // Each node is its own strongly connected component + assertEquals(4, result, "There should be 4 strongly connected components."); + } + + @Test + public void testComplexGraph() { + // Create a more complex graph with multiple SCCs + HashMap> adjList = new HashMap<>(); + adjList.put(0, new ArrayList<>(List.of(1))); + adjList.put(1, new ArrayList<>(List.of(2))); + adjList.put(2, new ArrayList<>(List.of(0, 3))); + adjList.put(3, new ArrayList<>(List.of(4))); + adjList.put(4, new ArrayList<>(List.of(5))); + adjList.put(5, new ArrayList<>(List.of(3))); + + int result = sccOptimized.getOutput(adjList, 6); + + // There are 2 SCCs: {0, 1, 2} and {3, 4, 5} + assertEquals(2, result, "There should be 2 strongly connected components."); + } +} diff --git a/src/test/java/com/thealgorithms/graph/TravelingSalesmanTest.java b/src/test/java/com/thealgorithms/graph/TravelingSalesmanTest.java new file mode 100644 index 000000000000..b93c9f89c944 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/TravelingSalesmanTest.java @@ -0,0 +1,127 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class TravelingSalesmanTest { + + // Test Case 1: A simple distance matrix with 4 cities + @Test + public void testBruteForceSimple() { + int[][] distanceMatrix = {{0, 10, 15, 20}, {10, 0, 35, 25}, {15, 35, 0, 30}, {20, 25, 30, 0}}; + int expectedMinDistance = 80; + int result = TravelingSalesman.bruteForce(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + @Test + public void testDynamicProgrammingSimple() { + int[][] distanceMatrix = {{0, 10, 15, 20}, {10, 0, 35, 25}, {15, 35, 0, 30}, {20, 25, 30, 0}}; + int expectedMinDistance = 80; + int result = TravelingSalesman.dynamicProgramming(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + // Test Case 2: A distance matrix with 3 cities + @Test + public void testBruteForceThreeCities() { + int[][] distanceMatrix = {{0, 10, 15}, {10, 0, 35}, {15, 35, 0}}; + int expectedMinDistance = 60; + int result = TravelingSalesman.bruteForce(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + @Test + public void testDynamicProgrammingThreeCities() { + int[][] distanceMatrix = {{0, 10, 15}, {10, 0, 35}, {15, 35, 0}}; + int expectedMinDistance = 60; + int result = TravelingSalesman.dynamicProgramming(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + // Test Case 3: A distance matrix with 5 cities (larger input) + @Test + public void testBruteForceFiveCities() { + int[][] distanceMatrix = {{0, 2, 9, 10, 1}, {2, 0, 6, 5, 8}, {9, 6, 0, 4, 3}, {10, 5, 4, 0, 7}, {1, 8, 3, 7, 0}}; + int expectedMinDistance = 15; + int result = TravelingSalesman.bruteForce(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + @Test + public void testDynamicProgrammingFiveCities() { + int[][] distanceMatrix = {{0, 2, 9, 10, 1}, {2, 0, 6, 5, 8}, {9, 6, 0, 4, 3}, {10, 5, 4, 0, 7}, {1, 8, 3, 7, 0}}; + int expectedMinDistance = 15; + int result = TravelingSalesman.dynamicProgramming(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + // Test Case 4: A distance matrix with 2 cities (simple case) + @Test + public void testBruteForceTwoCities() { + int[][] distanceMatrix = {{0, 1}, {1, 0}}; + int expectedMinDistance = 2; + int result = TravelingSalesman.bruteForce(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + @Test + public void testDynamicProgrammingTwoCities() { + int[][] distanceMatrix = {{0, 1}, {1, 0}}; + int expectedMinDistance = 2; + int result = TravelingSalesman.dynamicProgramming(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + // Test Case 5: A distance matrix with identical distances + @Test + public void testBruteForceEqualDistances() { + int[][] distanceMatrix = {{0, 10, 10, 10}, {10, 0, 10, 10}, {10, 10, 0, 10}, {10, 10, 10, 0}}; + int expectedMinDistance = 40; + int result = TravelingSalesman.bruteForce(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + @Test + public void testDynamicProgrammingEqualDistances() { + int[][] distanceMatrix = {{0, 10, 10, 10}, {10, 0, 10, 10}, {10, 10, 0, 10}, {10, 10, 10, 0}}; + int expectedMinDistance = 40; + int result = TravelingSalesman.dynamicProgramming(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + // Test Case 6: A distance matrix with only one city + @Test + public void testBruteForceOneCity() { + int[][] distanceMatrix = {{0}}; + int expectedMinDistance = 0; + int result = TravelingSalesman.bruteForce(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + @Test + public void testDynamicProgrammingOneCity() { + int[][] distanceMatrix = {{0}}; + int expectedMinDistance = 0; + int result = TravelingSalesman.dynamicProgramming(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + // Test Case 7: Distance matrix with large numbers + @Test + public void testBruteForceLargeNumbers() { + int[][] distanceMatrix = {{0, 1000000, 2000000}, {1000000, 0, 1500000}, {2000000, 1500000, 0}}; + int expectedMinDistance = 4500000; + int result = TravelingSalesman.bruteForce(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + @Test + public void testDynamicProgrammingLargeNumbers() { + int[][] distanceMatrix = {{0, 1000000, 2000000}, {1000000, 0, 1500000}, {2000000, 1500000, 0}}; + int expectedMinDistance = 4500000; + int result = TravelingSalesman.dynamicProgramming(distanceMatrix); + assertEquals(expectedMinDistance, result); + } +} diff --git a/src/test/java/com/thealgorithms/greedyalgorithms/ActivitySelectionTest.java b/src/test/java/com/thealgorithms/greedyalgorithms/ActivitySelectionTest.java index a997c198a39b..d24264a321bc 100644 --- a/src/test/java/com/thealgorithms/greedyalgorithms/ActivitySelectionTest.java +++ b/src/test/java/com/thealgorithms/greedyalgorithms/ActivitySelectionTest.java @@ -1,9 +1,11 @@ package com.thealgorithms.greedyalgorithms; +import static java.util.Collections.singletonList; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import org.junit.jupiter.api.Test; public class ActivitySelectionTest { @@ -24,7 +26,7 @@ public void testSingleActivity() { int[] end = {2}; ArrayList result = ActivitySelection.activitySelection(start, end); - ArrayList expected = new ArrayList<>(Arrays.asList(0)); + List expected = singletonList(0); assertEquals(expected, result); } diff --git a/src/test/java/com/thealgorithms/greedyalgorithms/BandwidthAllocationTest.java b/src/test/java/com/thealgorithms/greedyalgorithms/BandwidthAllocationTest.java new file mode 100644 index 000000000000..452f7858c162 --- /dev/null +++ b/src/test/java/com/thealgorithms/greedyalgorithms/BandwidthAllocationTest.java @@ -0,0 +1,22 @@ +package com.thealgorithms.greedyalgorithms; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class BandwidthAllocationTest { + + @ParameterizedTest + @MethodSource("bandwidthProvider") + public void testMaxValue(int capacity, int[] bandwidths, int[] values, int expected) { + assertEquals(expected, BandwidthAllocation.maxValue(capacity, bandwidths, values)); + } + + private static Stream bandwidthProvider() { + return Stream.of(Arguments.of(50, new int[] {20, 10, 30}, new int[] {40, 20, 30}, 80), Arguments.of(0, new int[] {5, 10}, new int[] {10, 20}, 0), Arguments.of(5, new int[] {5, 10}, new int[] {10, 20}, 10), Arguments.of(15, new int[] {10, 20}, new int[] {10, 25}, 18), + Arguments.of(25, new int[] {10, 15, 20}, new int[] {10, 30, 50}, 60)); + } +} diff --git a/src/test/java/com/thealgorithms/greedyalgorithms/CoinChangeTest.java b/src/test/java/com/thealgorithms/greedyalgorithms/CoinChangeTest.java index e9d267712a05..b9745be63088 100644 --- a/src/test/java/com/thealgorithms/greedyalgorithms/CoinChangeTest.java +++ b/src/test/java/com/thealgorithms/greedyalgorithms/CoinChangeTest.java @@ -1,9 +1,11 @@ package com.thealgorithms.greedyalgorithms; +import static java.util.Collections.singletonList; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import org.junit.jupiter.api.Test; public class CoinChangeTest { @@ -16,7 +18,7 @@ public void testCoinChangeProblemWithValidAmount() { @Test public void testCoinChangeProblemWithLargeAmount() { - ArrayList expected = new ArrayList<>(Arrays.asList(2000)); + List expected = singletonList(2000); ArrayList coins = CoinChange.coinChangeProblem(2000); assertEquals(expected, coins); } diff --git a/src/test/java/com/thealgorithms/greedyalgorithms/EgyptianFractionTest.java b/src/test/java/com/thealgorithms/greedyalgorithms/EgyptianFractionTest.java new file mode 100644 index 000000000000..1d34876de864 --- /dev/null +++ b/src/test/java/com/thealgorithms/greedyalgorithms/EgyptianFractionTest.java @@ -0,0 +1,22 @@ +package com.thealgorithms.greedyalgorithms; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +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; + +public class EgyptianFractionTest { + + @ParameterizedTest + @MethodSource("fractionProvider") + public void testGetEgyptianFraction(int numerator, int denominator, List expected) { + assertEquals(expected, EgyptianFraction.getEgyptianFraction(numerator, denominator)); + } + + private static Stream fractionProvider() { + return Stream.of(Arguments.of(2, 3, List.of("1/2", "1/6")), Arguments.of(3, 10, List.of("1/4", "1/20")), Arguments.of(1, 3, List.of("1/3")), Arguments.of(1, 2, List.of("1/2")), Arguments.of(4, 13, List.of("1/4", "1/18", "1/468"))); + } +} diff --git a/src/test/java/com/thealgorithms/greedyalgorithms/KCentersTest.java b/src/test/java/com/thealgorithms/greedyalgorithms/KCentersTest.java new file mode 100644 index 000000000000..9202d3467f0c --- /dev/null +++ b/src/test/java/com/thealgorithms/greedyalgorithms/KCentersTest.java @@ -0,0 +1,15 @@ +package com.thealgorithms.greedyalgorithms; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class KCentersTest { + + @Test + public void testFindKCenters() { + int[][] distances = {{0, 2, 3, 4}, {2, 0, 5, 1}, {3, 5, 0, 7}, {4, 1, 7, 0}}; + assertEquals(4, KCenters.findKCenters(distances, 2)); + assertEquals(2, KCenters.findKCenters(distances, 4)); + } +} diff --git a/src/test/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTimeTest.java b/src/test/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTimeTest.java new file mode 100644 index 000000000000..64cb4b80f182 --- /dev/null +++ b/src/test/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTimeTest.java @@ -0,0 +1,21 @@ +package com.thealgorithms.greedyalgorithms; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class MinimumWaitingTimeTest { + + @ParameterizedTest + @MethodSource("provideTestCases") + public void testMinimumWaitingTime(int[] queries, int expected) { + assertEquals(expected, MinimumWaitingTime.minimumWaitingTime(queries)); + } + + private static Stream provideTestCases() { + return Stream.of(Arguments.of(new int[] {3, 2, 1, 2, 6}, 17), Arguments.of(new int[] {3, 2, 1}, 4), Arguments.of(new int[] {1, 2, 3, 4}, 10), Arguments.of(new int[] {5, 5, 5, 5}, 30), Arguments.of(new int[] {}, 0)); + } +} diff --git a/src/test/java/com/thealgorithms/greedyalgorithms/OptimalFileMergingTest.java b/src/test/java/com/thealgorithms/greedyalgorithms/OptimalFileMergingTest.java new file mode 100644 index 000000000000..9ff2b95ff2e5 --- /dev/null +++ b/src/test/java/com/thealgorithms/greedyalgorithms/OptimalFileMergingTest.java @@ -0,0 +1,22 @@ +package com.thealgorithms.greedyalgorithms; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class OptimalFileMergingTest { + + @ParameterizedTest + @MethodSource("fileMergingProvider") + public void testMinMergeCost(int[] files, int expected) { + assertEquals(expected, OptimalFileMerging.minMergeCost(files)); + } + + private static Stream fileMergingProvider() { + return Stream.of(Arguments.of(new int[] {4, 3, 2, 6}, 29), Arguments.of(new int[] {5}, 0), Arguments.of(new int[] {2, 2, 2}, 10), Arguments.of(new int[] {10, 5, 3, 2}, 35), Arguments.of(new int[] {1, 1, 1, 1}, 8), Arguments.of(new int[] {1, 2, 3, 4, 5}, 33), + Arguments.of(new int[] {1, 2, 3, 4, 5, 6}, 51), Arguments.of(new int[] {1, 2, 3, 4, 5, 6, 7}, 74), Arguments.of(new int[] {1, 2, 3, 4, 5, 6, 7, 8}, 102), Arguments.of(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9}, 135)); + } +} diff --git a/src/test/java/com/thealgorithms/greedyalgorithms/StockProfitCalculatorTest.java b/src/test/java/com/thealgorithms/greedyalgorithms/StockProfitCalculatorTest.java new file mode 100644 index 000000000000..afad76a34521 --- /dev/null +++ b/src/test/java/com/thealgorithms/greedyalgorithms/StockProfitCalculatorTest.java @@ -0,0 +1,21 @@ +package com.thealgorithms.greedyalgorithms; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class StockProfitCalculatorTest { + + @ParameterizedTest + @MethodSource("provideTestCases") + public void testMaxProfit(int[] prices, int expected) { + assertEquals(expected, StockProfitCalculator.maxProfit(prices)); + } + + private static Stream provideTestCases() { + return Stream.of(Arguments.of(new int[] {7, 1, 5, 3, 6, 4}, 5), Arguments.of(new int[] {7, 6, 4, 3, 1}, 0), Arguments.of(new int[] {5, 5, 5, 5, 5}, 0), Arguments.of(new int[] {10}, 0), Arguments.of(new int[] {1, 5}, 4), Arguments.of(new int[] {2, 4, 1, 3, 7, 5}, 6)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/AbsoluteMaxTest.java b/src/test/java/com/thealgorithms/maths/AbsoluteMaxTest.java index 70d2f64bc541..33461fbbc088 100644 --- a/src/test/java/com/thealgorithms/maths/AbsoluteMaxTest.java +++ b/src/test/java/com/thealgorithms/maths/AbsoluteMaxTest.java @@ -19,4 +19,12 @@ void testGetMaxValue() { void testGetMaxValueWithNoArguments() { assertThrows(IllegalArgumentException.class, AbsoluteMax::getMaxValue); } + + @Test + void testGetMaxValueWithSameAbsoluteValues() { + assertEquals(5, AbsoluteMax.getMaxValue(-5, 5)); + assertEquals(5, AbsoluteMax.getMaxValue(5, -5)); + assertEquals(12, AbsoluteMax.getMaxValue(-12, 9, 3, 12, 1)); + assertEquals(12, AbsoluteMax.getMaxValue(12, 9, 3, -12, 1)); + } } diff --git a/src/test/java/com/thealgorithms/maths/AbsoluteMinTest.java b/src/test/java/com/thealgorithms/maths/AbsoluteMinTest.java index 4b676ca634f7..dfca757fd877 100644 --- a/src/test/java/com/thealgorithms/maths/AbsoluteMinTest.java +++ b/src/test/java/com/thealgorithms/maths/AbsoluteMinTest.java @@ -15,7 +15,13 @@ void testGetMinValue() { @Test void testGetMinValueWithNoArguments() { - Exception exception = assertThrows(IllegalArgumentException.class, () -> AbsoluteMin.getMinValue()); + Exception exception = assertThrows(IllegalArgumentException.class, AbsoluteMin::getMinValue); assertEquals("Numbers array cannot be empty", exception.getMessage()); } + + @Test + void testGetMinValueWithSameAbsoluteValues() { + assertEquals(-5, AbsoluteMin.getMinValue(-5, 5)); + assertEquals(-5, AbsoluteMin.getMinValue(5, -5)); + } } diff --git a/src/test/java/com/thealgorithms/maths/CatalanNumbersTest.java b/src/test/java/com/thealgorithms/maths/CatalanNumbersTest.java new file mode 100644 index 000000000000..2248ffd93732 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/CatalanNumbersTest.java @@ -0,0 +1,43 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Test class for CatalanNumbers + */ +class CatalanNumbersTest { + + /** + * Provides test data for the parameterized Catalan number test. + * Each array contains two elements: + * [input number, expected Catalan number for that input] + */ + static Stream catalanNumbersProvider() { + return Stream.of(new Object[] {0, 1}, new Object[] {1, 1}, new Object[] {2, 2}, new Object[] {3, 5}, new Object[] {4, 14}, new Object[] {5, 42}, new Object[] {6, 132}, new Object[] {7, 429}, new Object[] {8, 1430}, new Object[] {9, 4862}, new Object[] {10, 16796}); + } + + /** + * Parameterized test for checking the correctness of Catalan numbers. + * Uses the data from the provider method 'catalanNumbersProvider'. + */ + @ParameterizedTest + @MethodSource("catalanNumbersProvider") + void testCatalanNumbers(int input, int expected) { + assertEquals(expected, CatalanNumbers.catalan(input), () -> String.format("Catalan number for input %d should be %d", input, expected)); + } + + /** + * Test for invalid inputs which should throw an IllegalArgumentException. + */ + @Test + void testIllegalInput() { + assertThrows(IllegalArgumentException.class, () -> CatalanNumbers.catalan(-1)); + assertThrows(IllegalArgumentException.class, () -> CatalanNumbers.catalan(-5)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/ChineseRemainderTheoremTest.java b/src/test/java/com/thealgorithms/maths/ChineseRemainderTheoremTest.java new file mode 100644 index 000000000000..7c153ae4cdda --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/ChineseRemainderTheoremTest.java @@ -0,0 +1,55 @@ +package com.thealgorithms.maths; + +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class ChineseRemainderTheoremTest { + @Test + public void testCRTSimpleCase() { + List remainders = Arrays.asList(2, 3, 2); + List moduli = Arrays.asList(3, 5, 7); + int expected = 23; + int result = ChineseRemainderTheorem.solveCRT(remainders, moduli); + assertEquals(expected, result); + } + + @Test + public void testCRTLargeModuli() { + List remainders = Arrays.asList(1, 2, 3); + List moduli = Arrays.asList(5, 7, 9); + int expected = 156; + int result = ChineseRemainderTheorem.solveCRT(remainders, moduli); + assertEquals(expected, result); + } + + @Test + public void testCRTWithSingleCongruence() { + List remainders = singletonList(4); + List moduli = singletonList(7); + int expected = 4; + int result = ChineseRemainderTheorem.solveCRT(remainders, moduli); + assertEquals(expected, result); + } + + @Test + public void testCRTWithMultipleSolutions() { + List remainders = Arrays.asList(0, 3); + List moduli = Arrays.asList(4, 5); + int expected = 8; + int result = ChineseRemainderTheorem.solveCRT(remainders, moduli); + assertEquals(expected, result); + } + + @Test + public void testCRTLargeNumbers() { + List remainders = Arrays.asList(0, 4, 6); + List moduli = Arrays.asList(11, 13, 17); + int expected = 550; + int result = ChineseRemainderTheorem.solveCRT(remainders, moduli); + assertEquals(expected, result); + } +} diff --git a/src/test/java/com/thealgorithms/maths/ConvolutionFFTTest.java b/src/test/java/com/thealgorithms/maths/ConvolutionFFTTest.java new file mode 100644 index 000000000000..4d627f939d42 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/ConvolutionFFTTest.java @@ -0,0 +1,55 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +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; + +public class ConvolutionFFTTest { + + /** + * Helper method to create a complex signal from an array of doubles. + */ + private ArrayList createComplexSignal(double[] values) { + ArrayList signal = new ArrayList<>(); + for (double value : values) { + signal.add(new FFT.Complex(value, 0)); + } + return signal; + } + + /** + * Helper method to compare two complex signals for equality within a small margin of error. + */ + private void assertComplexArrayEquals(List expected, List result, double delta) { + assertEquals(expected.size(), result.size(), "Signal lengths are not equal."); + for (int i = 0; i < expected.size(); i++) { + FFT.Complex expectedValue = expected.get(i); + FFT.Complex resultValue = result.get(i); + assertEquals(expectedValue.real(), resultValue.real(), delta, "Real part mismatch at index " + i); + assertEquals(expectedValue.imaginary(), resultValue.imaginary(), delta, "Imaginary part mismatch at index " + i); + } + } + + @ParameterizedTest(name = "Test case {index}: {3}") + @MethodSource("provideTestCases") + public void testConvolutionFFT(double[] a, double[] b, double[] expectedOutput, String testDescription) { + ArrayList signalA = createComplexSignal(a); + ArrayList signalB = createComplexSignal(b); + + ArrayList expected = createComplexSignal(expectedOutput); + ArrayList result = ConvolutionFFT.convolutionFFT(signalA, signalB); + + assertComplexArrayEquals(expected, result, 1e-9); // Allow small margin of error + } + + private static Stream provideTestCases() { + return Stream.of(Arguments.of(new double[] {1, 2, 3}, new double[] {4, 5, 6}, new double[] {4, 13, 28, 27, 18}, "Basic test"), Arguments.of(new double[] {0, 0, 0}, new double[] {1, 2, 3}, new double[] {0, 0, 0, 0, 0}, "Test with zero elements"), + Arguments.of(new double[] {1, 2}, new double[] {3, 4, 5}, new double[] {3, 10, 13, 10}, "Test with different sizes"), Arguments.of(new double[] {5}, new double[] {2}, new double[] {10}, "Test with single element"), + Arguments.of(new double[] {1, -2, 3}, new double[] {-1, 2, -3}, new double[] {-1, 4, -10, 12, -9}, "Test with negative values")); + } +} diff --git a/src/test/java/com/thealgorithms/maths/ConvolutionTest.java b/src/test/java/com/thealgorithms/maths/ConvolutionTest.java new file mode 100644 index 000000000000..d57b3b3ca4e5 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/ConvolutionTest.java @@ -0,0 +1,27 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class ConvolutionTest { + + record ConvolutionTestCase(String description, double[] signalA, double[] signalB, double[] expected) { + } + + @ParameterizedTest(name = "{0}") + @MethodSource("provideTestCases") + void testConvolution(ConvolutionTestCase testCase) { + double[] result = Convolution.convolution(testCase.signalA, testCase.signalB); + assertArrayEquals(testCase.expected, result, 1e-9, testCase.description); + } + + private static Stream provideTestCases() { + return Stream.of(new ConvolutionTestCase("Basic convolution", new double[] {1, 2, 3}, new double[] {4, 5, 6}, new double[] {4, 13, 28, 27, 18}), new ConvolutionTestCase("Convolution with zero elements", new double[] {0, 0, 0}, new double[] {1, 2, 3}, new double[] {0, 0, 0, 0, 0}), + new ConvolutionTestCase("Convolution with single element", new double[] {2}, new double[] {3}, new double[] {6}), new ConvolutionTestCase("Convolution with different sizes", new double[] {1, 2}, new double[] {3, 4, 5}, new double[] {3, 10, 13, 10}), + new ConvolutionTestCase("Convolution with negative values", new double[] {1, -2, 3}, new double[] {-1, 2, -3}, new double[] {-1, 4, -10, 12, -9}), + new ConvolutionTestCase("Convolution with large numbers", new double[] {1e6, 2e6}, new double[] {3e6, 4e6}, new double[] {3e12, 1e13, 8e12})); + } +} diff --git a/src/test/java/com/thealgorithms/maths/EulerMethodTest.java b/src/test/java/com/thealgorithms/maths/EulerMethodTest.java new file mode 100644 index 000000000000..5ae5ac70b1df --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/EulerMethodTest.java @@ -0,0 +1,79 @@ +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 java.util.ArrayList; +import java.util.function.BiFunction; +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 EulerMethodTest { + + private static class EulerFullTestCase { + double[] params; + BiFunction equation; + int expectedSize; + double[] expectedFirstPoint; + double[] expectedLastPoint; + + EulerFullTestCase(double[] params, BiFunction equation, int expectedSize, double[] expectedFirstPoint, double[] expectedLastPoint) { + this.params = params; + this.equation = equation; + this.expectedSize = expectedSize; + this.expectedFirstPoint = expectedFirstPoint; + this.expectedLastPoint = expectedLastPoint; + } + } + + @ParameterizedTest + @MethodSource("eulerStepTestCases") + void testEulerStep(double x, double h, double y, BiFunction equation, double expected) { + double result = EulerMethod.eulerStep(x, h, y, equation); + assertEquals(expected, result, 1e-9, "Euler step failed for the given equation."); + } + + static Stream eulerStepTestCases() { + return Stream.of(Arguments.of(0.0, 0.1, 1.0, (BiFunction) ((x, y) -> x + y), 1.1)); + } + + @ParameterizedTest + @MethodSource("eulerStepInvalidCases") + void testEulerStepInvalidInput(double x, double h, double y, BiFunction equation, Class expectedExceptionClass) { + assertThrows(expectedExceptionClass, () -> EulerMethod.eulerStep(x, h, y, equation)); + } + + static Stream eulerStepInvalidCases() { + BiFunction dummyEquation = (x, y) -> x + y; + return Stream.of(Arguments.of(0.0, -0.1, 1.0, dummyEquation, IllegalArgumentException.class), Arguments.of(0.0, 0.0, 1.0, dummyEquation, IllegalArgumentException.class)); + } + + @ParameterizedTest + @MethodSource("eulerFullTestCases") + void testEulerFull(EulerFullTestCase testCase) { + ArrayList result = EulerMethod.eulerFull(testCase.params[0], testCase.params[1], testCase.params[2], testCase.params[3], testCase.equation); + assertEquals(testCase.expectedSize, result.size(), "Incorrect number of points in the result."); + assertArrayEquals(testCase.expectedFirstPoint, result.get(0), 1e-9, "Incorrect first point."); + assertArrayEquals(testCase.expectedLastPoint, result.get(result.size() - 1), 1e-9, "Incorrect last point."); + } + + static Stream eulerFullTestCases() { + return Stream.of(Arguments.of(new EulerFullTestCase(new double[] {0.0, 1.0, 0.5, 0.0}, (x, y) -> x, 3, new double[] {0.0, 0.0}, new double[] {1.0, 0.25})), + Arguments.of(new EulerFullTestCase(new double[] {0.0, 1.0, 0.1, 1.0}, (x, y) -> y, 12, new double[] {0.0, 1.0}, new double[] {1.0999999999999999, 2.8531167061100002})), + Arguments.of(new EulerFullTestCase(new double[] {0.0, 0.1, 0.1, 1.0}, (x, y) -> x + y, 2, new double[] {0.0, 1.0}, new double[] {0.1, 1.1}))); + } + + @ParameterizedTest + @MethodSource("eulerFullInvalidCases") + void testEulerFullInvalidInput(double xStart, double xEnd, double stepSize, double yInitial, BiFunction equation, Class expectedExceptionClass) { + assertThrows(expectedExceptionClass, () -> EulerMethod.eulerFull(xStart, xEnd, stepSize, yInitial, equation)); + } + + static Stream eulerFullInvalidCases() { + BiFunction dummyEquation = (x, y) -> x + y; + return Stream.of(Arguments.of(1.0, 0.0, 0.1, 1.0, dummyEquation, IllegalArgumentException.class), Arguments.of(0.0, 1.0, 0.0, 1.0, dummyEquation, IllegalArgumentException.class), Arguments.of(0.0, 1.0, -0.1, 1.0, dummyEquation, IllegalArgumentException.class)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/FastExponentiationTest.java b/src/test/java/com/thealgorithms/maths/FastExponentiationTest.java new file mode 100644 index 000000000000..f117f90233e3 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/FastExponentiationTest.java @@ -0,0 +1,67 @@ +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; + +/** + * Unit tests for the {@link FastExponentiation} class. + * + *

This class contains various test cases to verify the correctness of the fastExponentiation method. + * It covers basic functionality, edge cases, and exceptional cases. + */ +class FastExponentiationTest { + + /** + * Tests fast exponentiation with small numbers. + */ + @Test + void testSmallNumbers() { + assertEquals(1024, FastExponentiation.fastExponentiation(2, 10, 10000), "2^10 mod 10000 should be 1024"); + assertEquals(81, FastExponentiation.fastExponentiation(3, 4, 1000), "3^4 mod 1000 should be 81"); + } + + /** + * Tests the behavior of the fast exponentiation method when using a modulus. + */ + @Test + void testWithModulo() { + assertEquals(24, FastExponentiation.fastExponentiation(2, 10, 1000), "2^10 mod 1000 should be 24"); + assertEquals(0, FastExponentiation.fastExponentiation(10, 5, 10), "10^5 mod 10 should be 0"); + } + + /** + * Tests the edge cases where base or exponent is 0. + */ + @Test + void testBaseCases() { + assertEquals(1, FastExponentiation.fastExponentiation(2, 0, 1000), "Any number raised to the power 0 mod anything should be 1"); + assertEquals(0, FastExponentiation.fastExponentiation(0, 10, 1000), "0 raised to any power should be 0"); + assertEquals(1, FastExponentiation.fastExponentiation(0, 0, 1000), "0^0 is considered 0 in modular arithmetic."); + } + + /** + * Tests fast exponentiation with a negative base to ensure correctness under modular arithmetic. + */ + @Test + void testNegativeBase() { + assertEquals(9765625, FastExponentiation.fastExponentiation(-5, 10, 1000000007), "-5^10 mod 1000000007 should be 9765625"); + } + + /** + * Tests that a negative exponent throws an ArithmeticException. + */ + @Test + void testNegativeExponent() { + assertThrows(ArithmeticException.class, () -> { FastExponentiation.fastExponentiation(2, -5, 1000); }); + } + + /** + * Tests that the method throws an IllegalArgumentException for invalid modulus values. + */ + @Test + void testInvalidModulus() { + assertThrows(IllegalArgumentException.class, () -> { FastExponentiation.fastExponentiation(2, 5, 0); }); + } +} diff --git a/src/test/java/com/thealgorithms/maths/FindKthNumberTest.java b/src/test/java/com/thealgorithms/maths/FindKthNumberTest.java index 21285a527c37..ca69e66c9f6a 100644 --- a/src/test/java/com/thealgorithms/maths/FindKthNumberTest.java +++ b/src/test/java/com/thealgorithms/maths/FindKthNumberTest.java @@ -14,21 +14,30 @@ public void testFindKthMaxTypicalCases() { assertEquals(3, FindKthNumber.findKthMax(array1, 3)); assertEquals(4, FindKthNumber.findKthMax(array1, 2)); assertEquals(5, FindKthNumber.findKthMax(array1, 1)); + assertEquals(3, FindKthNumber.findKthMaxUsingHeap(array1, 3)); + assertEquals(4, FindKthNumber.findKthMaxUsingHeap(array1, 2)); + assertEquals(5, FindKthNumber.findKthMaxUsingHeap(array1, 1)); int[] array2 = {7, 5, 8, 2, 1, 6}; assertEquals(5, FindKthNumber.findKthMax(array2, 4)); assertEquals(6, FindKthNumber.findKthMax(array2, 3)); assertEquals(8, FindKthNumber.findKthMax(array2, 1)); + assertEquals(5, FindKthNumber.findKthMaxUsingHeap(array2, 4)); + assertEquals(6, FindKthNumber.findKthMaxUsingHeap(array2, 3)); + assertEquals(8, FindKthNumber.findKthMaxUsingHeap(array2, 1)); } @Test public void testFindKthMaxEdgeCases() { int[] array1 = {1}; assertEquals(1, FindKthNumber.findKthMax(array1, 1)); + assertEquals(1, FindKthNumber.findKthMaxUsingHeap(array1, 1)); int[] array2 = {5, 3}; assertEquals(5, FindKthNumber.findKthMax(array2, 1)); assertEquals(3, FindKthNumber.findKthMax(array2, 2)); + assertEquals(5, FindKthNumber.findKthMaxUsingHeap(array2, 1)); + assertEquals(3, FindKthNumber.findKthMaxUsingHeap(array2, 2)); } @Test @@ -36,6 +45,8 @@ public void testFindKthMaxInvalidK() { int[] array = {1, 2, 3, 4, 5}; assertThrows(IllegalArgumentException.class, () -> FindKthNumber.findKthMax(array, 0)); assertThrows(IllegalArgumentException.class, () -> FindKthNumber.findKthMax(array, 6)); + assertThrows(IllegalArgumentException.class, () -> FindKthNumber.findKthMaxUsingHeap(array, 0)); + assertThrows(IllegalArgumentException.class, () -> FindKthNumber.findKthMaxUsingHeap(array, 6)); } @Test @@ -43,8 +54,10 @@ public void testFindKthMaxLargeArray() { int[] array = generateArray(1000); int k = new Random().nextInt(1, array.length); int result = FindKthNumber.findKthMax(array, k); + int maxK = FindKthNumber.findKthMaxUsingHeap(array, k); Arrays.sort(array); assertEquals(array[array.length - k], result); + assertEquals(array[array.length - k], maxK); } public static int[] generateArray(int capacity) { diff --git a/src/test/java/com/thealgorithms/maths/GCDTest.java b/src/test/java/com/thealgorithms/maths/GCDTest.java index 5a659664fd29..bac3f8f7596c 100644 --- a/src/test/java/com/thealgorithms/maths/GCDTest.java +++ b/src/test/java/com/thealgorithms/maths/GCDTest.java @@ -40,6 +40,11 @@ void test7() { Assertions.assertEquals(GCD.gcd(9, 6), 3); } + @Test + void test8() { + Assertions.assertEquals(GCD.gcd(48, 18, 30, 12), 6); + } + @Test void testArrayGcd1() { Assertions.assertEquals(GCD.gcd(new int[] {9, 6}), 3); diff --git a/src/test/java/com/thealgorithms/maths/GoldbachConjectureTest.java b/src/test/java/com/thealgorithms/maths/GoldbachConjectureTest.java new file mode 100644 index 000000000000..84c5824d26ae --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/GoldbachConjectureTest.java @@ -0,0 +1,29 @@ +package com.thealgorithms.maths; + +import static com.thealgorithms.maths.GoldbachConjecture.getPrimeSum; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class GoldbachConjectureTest { + @Test + void testValidEvenNumbers() { + assertEquals(new GoldbachConjecture.Result(3, 7), getPrimeSum(10)); // 10 = 3 + 7 + assertEquals(new GoldbachConjecture.Result(5, 7), getPrimeSum(12)); // 12 = 5 + 7 + assertEquals(new GoldbachConjecture.Result(3, 11), getPrimeSum(14)); // 14 = 3 + 11 + assertEquals(new GoldbachConjecture.Result(5, 13), getPrimeSum(18)); // 18 = 5 + 13 + } + @Test + void testInvalidOddNumbers() { + assertThrows(IllegalArgumentException.class, () -> getPrimeSum(7)); + assertThrows(IllegalArgumentException.class, () -> getPrimeSum(15)); + } + @Test + void testLesserThanTwo() { + assertThrows(IllegalArgumentException.class, () -> getPrimeSum(1)); + assertThrows(IllegalArgumentException.class, () -> getPrimeSum(2)); + assertThrows(IllegalArgumentException.class, () -> getPrimeSum(-5)); + assertThrows(IllegalArgumentException.class, () -> getPrimeSum(-26)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/KaratsubaMultiplicationTest.java b/src/test/java/com/thealgorithms/maths/KaratsubaMultiplicationTest.java new file mode 100644 index 000000000000..e184d998724a --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/KaratsubaMultiplicationTest.java @@ -0,0 +1,58 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.math.BigInteger; +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; + +/** + * Unit test class for {@link KaratsubaMultiplication} class. + * + *

+ * This class tests various edge cases and normal cases for the + * Karatsuba multiplication algorithm implemented in the KaratsubaMultiplication class. + * It uses parameterized tests to handle multiple test cases. + *

+ */ +class KaratsubaMultiplicationTest { + + /** + * Provides test data for the parameterized test. + * Each entry in the stream contains three elements: x, y, and the expected result. + * + * @return a stream of arguments for the parameterized test + */ + static Stream provideTestCases() { + return Stream.of( + // Test case 1: Two small numbers + Arguments.of(new BigInteger("1234"), new BigInteger("5678"), new BigInteger("7006652")), + // Test case 2: Two large numbers + Arguments.of(new BigInteger("342364"), new BigInteger("393958"), new BigInteger("134877036712")), + // Test case 3: One number is zero + Arguments.of(BigInteger.ZERO, new BigInteger("5678"), BigInteger.ZERO), + // Test case 4: Both numbers are zero + Arguments.of(BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO), + // Test case 5: Single-digit numbers + Arguments.of(new BigInteger("9"), new BigInteger("8"), new BigInteger("72"))); + } + + /** + * Parameterized test for Karatsuba multiplication. + * + *

+ * This method runs the Karatsuba multiplication algorithm for multiple test cases. + *

+ * + * @param x the first number to multiply + * @param y the second number to multiply + * @param expected the expected result of x * y + */ + @ParameterizedTest + @MethodSource("provideTestCases") + void testKaratsubaMultiplication(BigInteger x, BigInteger y, BigInteger expected) { + assertEquals(expected, KaratsubaMultiplication.karatsuba(x, y)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/KrishnamurthyNumberTest.java b/src/test/java/com/thealgorithms/maths/KrishnamurthyNumberTest.java new file mode 100644 index 000000000000..595acde2b5d8 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/KrishnamurthyNumberTest.java @@ -0,0 +1,62 @@ +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; + +/** + * Unit tests for the KrishnamurthyNumber class. + */ +public class KrishnamurthyNumberTest { + + /** + * Test the isKrishnamurthy method with a known Krishnamurthy number. + */ + @Test + public void testIsKrishnamurthyTrue() { + assertTrue(KrishnamurthyNumber.isKrishnamurthy(145)); + } + + /** + * Test the isKrishnamurthy method with a number that is not a Krishnamurthy number. + */ + @Test + public void testIsKrishnamurthyFalse() { + assertFalse(KrishnamurthyNumber.isKrishnamurthy(123)); + } + + /** + * Test the isKrishnamurthy method with zero. + */ + @Test + public void testIsKrishnamurthyZero() { + assertFalse(KrishnamurthyNumber.isKrishnamurthy(0)); + } + + /** + * Test the isKrishnamurthy method with a negative number. + */ + @Test + public void testIsKrishnamurthyNegative() { + assertFalse(KrishnamurthyNumber.isKrishnamurthy(-145)); + } + + /** + * Test the isKrishnamurthy method with a single-digit Krishnamurthy number. + */ + @Test + public void testIsKrishnamurthySingleDigitTrue() { + assertTrue(KrishnamurthyNumber.isKrishnamurthy(1)); + assertTrue(KrishnamurthyNumber.isKrishnamurthy(2)); + } + + /** + * Test the isKrishnamurthy method with a single-digit number that is not a Krishnamurthy number. + */ + @Test + public void testIsKrishnamurthySingleDigitFalse() { + assertFalse(KrishnamurthyNumber.isKrishnamurthy(3)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(4)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/MathBuilderTest.java b/src/test/java/com/thealgorithms/maths/MathBuilderTest.java new file mode 100644 index 000000000000..dc381bfca5d3 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/MathBuilderTest.java @@ -0,0 +1,52 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class MathBuilderTest { + + @Test + void simpleMath() { + double result = new MathBuilder.Builder(100).add(200).multiply(10).print().divideIf(20, (a, b) -> a % 2 == 0).sqrt().print().ceil().build().get(); + assertEquals(13, result); + } + + @Test + void memoryTest() { + long result = new MathBuilder.Builder().set(100).sqrt().remember().add(21).recallIf(a -> a < 50, true).mod(2).build().toLong(); + assertEquals(0, result); + } + + @Test + void freeFallDistance() { + long distance = new MathBuilder.Builder(9.81).multiply(0.5).multiply(5 * 5).round().build().toLong(); + assertEquals(123, distance); // Expected result: 0.5 * 9.81 * 25 = 122.625 ≈ 123 + } + + @Test + void batchSalaryProcessing() { + double[] salaries = {2000, 3000, 4000, 5000}; + long[] processedSalaries = new long[salaries.length]; + for (int i = 0; i < salaries.length; i++) { + processedSalaries[i] = new MathBuilder.Builder(salaries[i]).addIf(salaries[i] * 0.1, (sal, bonus) -> sal > 2500).multiply(0.92).round().build().toLong(); + } + long[] expectedSalaries = {1840, 3036, 4048, 5060}; + assertArrayEquals(expectedSalaries, processedSalaries); + } + + @Test + void parenthesis() { + // 10 + (20*5) - 40 + (100 / 10) = 80 + double result = new MathBuilder.Builder(10).openParenthesis(20).multiply(5).closeParenthesisAndPlus().minus(40).openParenthesis(100).divide(10).closeParenthesisAndPlus().build().get(); + assertEquals(80, result); + } + + @Test + void areaOfCircle() { + // Radius is 4 + double area = new MathBuilder.Builder().pi().openParenthesis(4).multiply(4).closeParenthesisAndMultiply().build().get(); + assertEquals(Math.PI * 4 * 4, area); + } +} diff --git a/src/test/java/com/thealgorithms/maths/MeansTest.java b/src/test/java/com/thealgorithms/maths/MeansTest.java index bb2b4b6d1c50..4b3a5df44b34 100644 --- a/src/test/java/com/thealgorithms/maths/MeansTest.java +++ b/src/test/java/com/thealgorithms/maths/MeansTest.java @@ -59,8 +59,7 @@ void arithmeticMeanMultipleNumbers() { @Test void geometricMeanMultipleNumbers() { - LinkedList numbers = new LinkedList<>(); - numbers.addAll(Lists.newArrayList(1d, 2d, 3d, 4d, 5d, 6d, 1.25)); + LinkedList numbers = new LinkedList<>(Lists.newArrayList(1d, 2d, 3d, 4d, 5d, 6d, 1.25)); assertEquals(2.6426195539300585, Means.geometric(numbers)); } diff --git a/src/test/java/com/thealgorithms/maths/PerfectSquareTest.java b/src/test/java/com/thealgorithms/maths/PerfectSquareTest.java index 08c96bc71f9b..2bda5bfd6cf8 100644 --- a/src/test/java/com/thealgorithms/maths/PerfectSquareTest.java +++ b/src/test/java/com/thealgorithms/maths/PerfectSquareTest.java @@ -9,11 +9,13 @@ public class PerfectSquareTest { @ValueSource(ints = {0, 1, 2 * 2, 3 * 3, 4 * 4, 5 * 5, 6 * 6, 7 * 7, 8 * 8, 9 * 9, 10 * 10, 11 * 11, 123 * 123}) void positiveTest(final int number) { Assertions.assertTrue(PerfectSquare.isPerfectSquare(number)); + Assertions.assertTrue(PerfectSquare.isPerfectSquareUsingPow(number)); } @ParameterizedTest @ValueSource(ints = {-1, -2, -3, -4, -5, -100, 2, 3, 5, 6, 7, 8, 10, 11, 12, 13, 15, 17, 99, 101, 257, 999, 1001}) void negativeTest(final int number) { Assertions.assertFalse(PerfectSquare.isPerfectSquare(number)); + Assertions.assertFalse(PerfectSquare.isPerfectSquareUsingPow(number)); } } diff --git a/src/test/java/com/thealgorithms/maths/PronicNumberTest.java b/src/test/java/com/thealgorithms/maths/PronicNumberTest.java index 5a31981bed5c..8bf2b1852652 100644 --- a/src/test/java/com/thealgorithms/maths/PronicNumberTest.java +++ b/src/test/java/com/thealgorithms/maths/PronicNumberTest.java @@ -1,33 +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.api.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; public class PronicNumberTest { - @Test - void testForPronicNumber() { - // given - int number = 30; - - // when - boolean result = PronicNumber.isPronic(number); - - // then - assertTrue(result); + @ParameterizedTest + @ValueSource(ints = {0, 2, 6, 12, 20, 30, 42, 110, 272, 380, 420, 1260, 2550}) + void testForPronicNumber(final int number) { + Assertions.assertTrue(PronicNumber.isPronic(number)); + Assertions.assertTrue(PronicNumber.isPronicNumber(number)); } - @Test - void testForNonPronicNumber() { - // given - int number = 21; - - // when - boolean result = PronicNumber.isPronic(number); - - // then - assertFalse(result); + @ParameterizedTest + @ValueSource(ints = {1, 4, 21, 36, 150, 2500}) + void testForNonPronicNumber(final int number) { + Assertions.assertFalse(PronicNumber.isPronic(number)); + Assertions.assertFalse(PronicNumber.isPronicNumber(number)); } } diff --git a/src/test/java/com/thealgorithms/maths/QuadraticEquationSolverTest.java b/src/test/java/com/thealgorithms/maths/QuadraticEquationSolverTest.java new file mode 100644 index 000000000000..a2046511ddf5 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/QuadraticEquationSolverTest.java @@ -0,0 +1,50 @@ +package com.thealgorithms.maths; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class QuadraticEquationSolverTest { + private final QuadraticEquationSolver quadraticEquationSolver = new QuadraticEquationSolver(); + + @Test + public void testSolveEquationRealRoots() { + // 4.2x^2 + 8x + 1.9 = 0 + double a = 4.2; + double b = 8; + double c = 1.9; + + ComplexNumber[] roots = quadraticEquationSolver.solveEquation(a, b, c); + Assertions.assertEquals(roots.length, 2); + Assertions.assertEquals(roots[0].real, -0.27810465435684306); + Assertions.assertNull(roots[0].imaginary); + Assertions.assertEquals(roots[1].real, -1.6266572504050616); + Assertions.assertNull(roots[1].imaginary); + } + + @Test + public void testSolveEquationEqualRoots() { + // x^2 + 2x + 1 = 0 + double a = 1; + double b = 2; + double c = 1; + + ComplexNumber[] roots = quadraticEquationSolver.solveEquation(a, b, c); + Assertions.assertEquals(roots.length, 1); + Assertions.assertEquals(roots[0].real, -1); + } + + @Test + public void testSolveEquationComplexRoots() { + // 2.3x^2 + 4x + 5.6 = 0 + double a = 2.3; + double b = 4; + 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); + } +} diff --git a/src/test/java/com/thealgorithms/maths/SolovayStrassenPrimalityTestTest.java b/src/test/java/com/thealgorithms/maths/SolovayStrassenPrimalityTestTest.java new file mode 100644 index 000000000000..18cc35266c8c --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/SolovayStrassenPrimalityTestTest.java @@ -0,0 +1,122 @@ +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.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Unit tests for the {@link SolovayStrassenPrimalityTest} class. + * This class tests the functionality of the Solovay-Strassen primality test implementation. + */ +class SolovayStrassenPrimalityTestTest { + + private static final int RANDOM_SEED = 123; // Seed for reproducibility + private SolovayStrassenPrimalityTest testInstance; + + /** + * Sets up a new instance of {@link SolovayStrassenPrimalityTest} + * before each test case, using a fixed random seed for consistency. + */ + @BeforeEach + void setUp() { + testInstance = SolovayStrassenPrimalityTest.getSolovayStrassenPrimalityTest(RANDOM_SEED); + } + + /** + * Provides test cases for prime numbers with various values of n and k (iterations). + * + * @return an array of objects containing pairs of n and k values + */ + static Object[][] primeNumbers() { + return new Object[][] {{2, 1}, {3, 1}, {5, 5}, {7, 10}, {11, 20}, {13, 10}, {17, 5}, {19, 1}}; + } + + /** + * Tests known prime numbers with various values of n and k (iterations). + * + * @param n the number to be tested for primality + * @param k the number of iterations to use in the primality test + */ + @ParameterizedTest + @MethodSource("primeNumbers") + void testPrimeNumbersWithDifferentNAndK(int n, int k) { + assertTrue(testInstance.solovayStrassen(n, k), n + " should be prime"); + } + + /** + * Provides test cases for composite numbers with various values of n and k (iterations). + * + * @return an array of objects containing pairs of n and k values + */ + static Object[][] compositeNumbers() { + return new Object[][] {{4, 1}, {6, 5}, {8, 10}, {9, 20}, {10, 1}, {12, 5}, {15, 10}}; + } + + /** + * Tests known composite numbers with various values of n and k (iterations). + * + * @param n the number to be tested for primality + * @param k the number of iterations to use in the primality test + */ + @ParameterizedTest + @MethodSource("compositeNumbers") + void testCompositeNumbersWithDifferentNAndK(int n, int k) { + assertFalse(testInstance.solovayStrassen(n, k), n + " should be composite"); + } + + /** + * Tests edge cases for the primality test. + * This includes negative numbers and small integers (0 and 1). + */ + @Test + void testEdgeCases() { + assertFalse(testInstance.solovayStrassen(-1, 10), "-1 should not be prime"); + assertFalse(testInstance.solovayStrassen(0, 10), "0 should not be prime"); + assertFalse(testInstance.solovayStrassen(1, 10), "1 should not be prime"); + + // Test small primes and composites + assertTrue(testInstance.solovayStrassen(2, 1), "2 is a prime number (single iteration)"); + assertFalse(testInstance.solovayStrassen(9, 1), "9 is a composite number (single iteration)"); + + // Test larger primes and composites + long largePrime = 104729; // Known large prime number + long largeComposite = 104730; // Composite number (even) + + assertTrue(testInstance.solovayStrassen(largePrime, 20), "104729 is a prime number"); + assertFalse(testInstance.solovayStrassen(largeComposite, 20), "104730 is a composite number"); + + // Test very large numbers (may take longer) + long veryLargePrime = 512927357; // Known very large prime number + long veryLargeComposite = 512927358; // Composite number (even) + + assertTrue(testInstance.solovayStrassen(veryLargePrime, 20), Long.MAX_VALUE - 1 + " is likely a prime number."); + + assertFalse(testInstance.solovayStrassen(veryLargeComposite, 20), Long.MAX_VALUE + " is a composite number."); + } + + /** + * Tests the Jacobi symbol calculation directly for known values. + * This verifies that the Jacobi symbol method behaves as expected. + */ + @Test + void testJacobiSymbolCalculation() { + // Jacobi symbol (a/n) where n is odd and positive + int jacobi1 = testInstance.calculateJacobi(6, 11); // Should return -1 + int jacobi2 = testInstance.calculateJacobi(5, 11); // Should return +1 + + assertEquals(-1, jacobi1); + assertEquals(+1, jacobi2); + + // Edge case: Jacobi symbol with even n or non-positive n + int jacobi4 = testInstance.calculateJacobi(5, -11); // Should return 0 (invalid) + int jacobi5 = testInstance.calculateJacobi(5, 0); // Should return 0 (invalid) + + assertEquals(0, jacobi4); + assertEquals(0, jacobi5); + } +} diff --git a/src/test/java/com/thealgorithms/maths/SquareFreeIntegerTest.java b/src/test/java/com/thealgorithms/maths/SquareFreeIntegerTest.java index d7e16e02602b..5b35ee7bd9d0 100644 --- a/src/test/java/com/thealgorithms/maths/SquareFreeIntegerTest.java +++ b/src/test/java/com/thealgorithms/maths/SquareFreeIntegerTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import com.thealgorithms.maths.Prime.SquareFreeInteger; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/thealgorithms/maths/SumOfOddNumbersTest.java b/src/test/java/com/thealgorithms/maths/SumOfOddNumbersTest.java new file mode 100644 index 000000000000..6470d7983888 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/SumOfOddNumbersTest.java @@ -0,0 +1,28 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.stream.Stream; +import org.junit.jupiter.api.Assertions; +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 SumOfOddNumbersTest { + + @ParameterizedTest + @MethodSource("inputStream") + void sumOfFirstNOddNumbersTests(int expected, int input) { + Assertions.assertEquals(expected, SumOfOddNumbers.sumOfFirstNOddNumbers(input)); + } + + private static Stream inputStream() { + return Stream.of(Arguments.of(1, 1), Arguments.of(4, 2), Arguments.of(9, 3), Arguments.of(16, 4), Arguments.of(25, 5), Arguments.of(100, 10)); + } + + @Test + public void testSumOfFirstNOddNumbersThrowsExceptionForNegativeInput() { + assertThrows(IllegalArgumentException.class, () -> SumOfOddNumbers.sumOfFirstNOddNumbers(-1)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/UniformNumbersTest.java b/src/test/java/com/thealgorithms/maths/UniformNumbersTest.java new file mode 100644 index 000000000000..ac46c00014ad --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/UniformNumbersTest.java @@ -0,0 +1,56 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class UniformNumbersTest { + + @Test + void testSingleUniformDigitRange() { + assertEquals(1, UniformNumbers.countUniformIntegers(1, 1)); + assertEquals(9, UniformNumbers.countUniformIntegers(1, 9)); + } + + @Test + void testSmallRange() { + assertEquals(1, UniformNumbers.countUniformIntegers(10, 11)); + assertEquals(2, UniformNumbers.countUniformIntegers(22, 33)); + } + + @Test + void testRangeWithNoUniformNumbers() { + assertEquals(0, UniformNumbers.countUniformIntegers(12, 21)); + assertEquals(0, UniformNumbers.countUniformIntegers(123, 128)); + } + + @Test + void testRangeWithAllUniformNumbers() { + assertEquals(9, UniformNumbers.countUniformIntegers(1, 9)); + assertEquals(18, UniformNumbers.countUniformIntegers(1, 99)); + } + + @Test + void testMultiDigitRangeWithUniformNumbers() { + assertEquals(1, UniformNumbers.countUniformIntegers(100, 111)); + assertEquals(2, UniformNumbers.countUniformIntegers(111, 222)); + } + + @Test + void testExactUniformBoundary() { + assertEquals(1, UniformNumbers.countUniformIntegers(111, 111)); + assertEquals(2, UniformNumbers.countUniformIntegers(111, 222)); + } + + @Test + void testLargeRange() { + assertEquals(27, UniformNumbers.countUniformIntegers(1, 999)); + assertEquals(36, UniformNumbers.countUniformIntegers(1, 9999)); + } + + @Test + void testInvalidRange() { + assertEquals(0, UniformNumbers.countUniformIntegers(500, 100)); + assertEquals(0, UniformNumbers.countUniformIntegers(-100, -1)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/VampireNumberTest.java b/src/test/java/com/thealgorithms/maths/VampireNumberTest.java new file mode 100644 index 000000000000..6f331f1252cd --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/VampireNumberTest.java @@ -0,0 +1,32 @@ +package com.thealgorithms.maths; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class VampireNumberTest { + @Test + void areVampireNumbers() { + Assertions.assertTrue(VampireNumber.isVampireNumber(15, 93, true)); + Assertions.assertTrue(VampireNumber.isVampireNumber(135, 801, true)); + Assertions.assertTrue(VampireNumber.isVampireNumber(201, 600, true)); + } + + @Test + void arePseudoVampireNumbers() { + Assertions.assertTrue(VampireNumber.isVampireNumber(150, 93, false)); + Assertions.assertTrue(VampireNumber.isVampireNumber(546, 84, false)); + Assertions.assertTrue(VampireNumber.isVampireNumber(641, 65, false)); + } + + @Test + void areNotVampireNumbers() { + Assertions.assertFalse(VampireNumber.isVampireNumber(51, 39, false)); + Assertions.assertFalse(VampireNumber.isVampireNumber(51, 39, true)); + } + + @Test + void testSplitIntoSortedDigits() { + Assertions.assertEquals("123", VampireNumber.splitIntoSortedDigits(321)); + Assertions.assertEquals("02234", VampireNumber.splitIntoSortedDigits(20, 324)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/LiouvilleLambdaFunctionTest.java b/src/test/java/com/thealgorithms/maths/prime/LiouvilleLambdaFunctionTest.java similarity index 94% rename from src/test/java/com/thealgorithms/maths/LiouvilleLambdaFunctionTest.java rename to src/test/java/com/thealgorithms/maths/prime/LiouvilleLambdaFunctionTest.java index a2763047acf0..d32815c0b8a9 100644 --- a/src/test/java/com/thealgorithms/maths/LiouvilleLambdaFunctionTest.java +++ b/src/test/java/com/thealgorithms/maths/prime/LiouvilleLambdaFunctionTest.java @@ -1,8 +1,9 @@ -package com.thealgorithms.maths; +package com.thealgorithms.maths.prime; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import com.thealgorithms.maths.Prime.LiouvilleLambdaFunction; import org.junit.jupiter.api.Test; class LiouvilleLambdaFunctionTest { diff --git a/src/test/java/com/thealgorithms/maths/MillerRabinPrimalityCheckTest.java b/src/test/java/com/thealgorithms/maths/prime/MillerRabinPrimalityCheckTest.java similarity index 92% rename from src/test/java/com/thealgorithms/maths/MillerRabinPrimalityCheckTest.java rename to src/test/java/com/thealgorithms/maths/prime/MillerRabinPrimalityCheckTest.java index d547cecf24cd..4defcd587758 100644 --- a/src/test/java/com/thealgorithms/maths/MillerRabinPrimalityCheckTest.java +++ b/src/test/java/com/thealgorithms/maths/prime/MillerRabinPrimalityCheckTest.java @@ -1,8 +1,9 @@ -package com.thealgorithms.maths; +package com.thealgorithms.maths.prime; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.thealgorithms.maths.Prime.MillerRabinPrimalityCheck; import org.junit.jupiter.api.Test; class MillerRabinPrimalityCheckTest { diff --git a/src/test/java/com/thealgorithms/maths/MobiusFunctionTest.java b/src/test/java/com/thealgorithms/maths/prime/MobiusFunctionTest.java similarity index 96% rename from src/test/java/com/thealgorithms/maths/MobiusFunctionTest.java rename to src/test/java/com/thealgorithms/maths/prime/MobiusFunctionTest.java index f3a6514ce633..734d02477ba2 100644 --- a/src/test/java/com/thealgorithms/maths/MobiusFunctionTest.java +++ b/src/test/java/com/thealgorithms/maths/prime/MobiusFunctionTest.java @@ -1,8 +1,9 @@ -package com.thealgorithms.maths; +package com.thealgorithms.maths.prime; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import com.thealgorithms.maths.Prime.MobiusFunction; import org.junit.jupiter.api.Test; class MobiusFunctionTest { diff --git a/src/test/java/com/thealgorithms/maths/PrimeCheckTest.java b/src/test/java/com/thealgorithms/maths/prime/PrimeCheckTest.java similarity index 89% rename from src/test/java/com/thealgorithms/maths/PrimeCheckTest.java rename to src/test/java/com/thealgorithms/maths/prime/PrimeCheckTest.java index c3e1634c51fe..2182bcd9cb16 100644 --- a/src/test/java/com/thealgorithms/maths/PrimeCheckTest.java +++ b/src/test/java/com/thealgorithms/maths/prime/PrimeCheckTest.java @@ -1,5 +1,6 @@ -package com.thealgorithms.maths; +package com.thealgorithms.maths.prime; +import com.thealgorithms.maths.Prime.PrimeCheck; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/thealgorithms/maths/PrimeFactorizationTest.java b/src/test/java/com/thealgorithms/maths/prime/PrimeFactorizationTest.java similarity index 93% rename from src/test/java/com/thealgorithms/maths/PrimeFactorizationTest.java rename to src/test/java/com/thealgorithms/maths/prime/PrimeFactorizationTest.java index 6994379d736a..79d685726261 100644 --- a/src/test/java/com/thealgorithms/maths/PrimeFactorizationTest.java +++ b/src/test/java/com/thealgorithms/maths/prime/PrimeFactorizationTest.java @@ -1,7 +1,8 @@ -package com.thealgorithms.maths; +package com.thealgorithms.maths.prime; import static org.junit.jupiter.api.Assertions.assertEquals; +import com.thealgorithms.maths.Prime.PrimeFactorization; import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/com/thealgorithms/misc/InverseOfMatrixTest.java b/src/test/java/com/thealgorithms/matrix/InverseOfMatrixTest.java similarity index 96% rename from src/test/java/com/thealgorithms/misc/InverseOfMatrixTest.java rename to src/test/java/com/thealgorithms/matrix/InverseOfMatrixTest.java index 2f20de444315..930fb377cd32 100644 --- a/src/test/java/com/thealgorithms/misc/InverseOfMatrixTest.java +++ b/src/test/java/com/thealgorithms/matrix/InverseOfMatrixTest.java @@ -1,5 +1,4 @@ -package com.thealgorithms.misc; - +package com.thealgorithms.matrix; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import java.util.stream.Stream; diff --git a/src/test/java/com/thealgorithms/maths/MatrixRankTest.java b/src/test/java/com/thealgorithms/matrix/MatrixRankTest.java similarity index 98% rename from src/test/java/com/thealgorithms/maths/MatrixRankTest.java rename to src/test/java/com/thealgorithms/matrix/MatrixRankTest.java index 415b84ec43f8..33a0196b7bf7 100644 --- a/src/test/java/com/thealgorithms/maths/MatrixRankTest.java +++ b/src/test/java/com/thealgorithms/matrix/MatrixRankTest.java @@ -1,4 +1,4 @@ -package com.thealgorithms.maths; +package com.thealgorithms.matrix; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/src/test/java/com/thealgorithms/matrix/MatrixTransposeTest.java b/src/test/java/com/thealgorithms/matrix/MatrixTransposeTest.java new file mode 100644 index 000000000000..0457f31418cf --- /dev/null +++ b/src/test/java/com/thealgorithms/matrix/MatrixTransposeTest.java @@ -0,0 +1,33 @@ +package com.thealgorithms.matrix; + +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; + +public class MatrixTransposeTest { + + private static Stream provideValidMatrixTestCases() { + return Stream.of(Arguments.of(new int[][] {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}, new int[][] {{1, 4, 7}, {2, 5, 8}, {3, 6, 9}}, "Transpose of square matrix"), Arguments.of(new int[][] {{1, 2}, {3, 4}, {5, 6}}, new int[][] {{1, 3, 5}, {2, 4, 6}}, "Transpose of rectangular matrix"), + Arguments.of(new int[][] {{1, 2, 3}}, new int[][] {{1}, {2}, {3}}, "Transpose of single-row matrix"), Arguments.of(new int[][] {{1}, {2}, {3}}, new int[][] {{1, 2, 3}}, "Transpose of single-column matrix")); + } + + private static Stream provideInvalidMatrixTestCases() { + return Stream.of(Arguments.of(new int[0][0], "Empty matrix should throw IllegalArgumentException"), Arguments.of(null, "Null matrix should throw IllegalArgumentException")); + } + + @ParameterizedTest(name = "Test case {index}: {2}") + @MethodSource("provideValidMatrixTestCases") + void testValidMatrixTranspose(int[][] input, int[][] expected, String description) { + assertArrayEquals(expected, MatrixTranspose.transpose(input), description); + } + + @ParameterizedTest(name = "Test case {index}: {1}") + @MethodSource("provideInvalidMatrixTestCases") + void testInvalidMatrixTranspose(int[][] input, String description) { + assertThrows(IllegalArgumentException.class, () -> MatrixTranspose.transpose(input), description); + } +} diff --git a/src/test/java/com/thealgorithms/maths/MatrixUtilTest.java b/src/test/java/com/thealgorithms/matrix/MatrixUtilTest.java similarity index 96% rename from src/test/java/com/thealgorithms/maths/MatrixUtilTest.java rename to src/test/java/com/thealgorithms/matrix/MatrixUtilTest.java index b954e6ff7511..78947b1e70cb 100644 --- a/src/test/java/com/thealgorithms/maths/MatrixUtilTest.java +++ b/src/test/java/com/thealgorithms/matrix/MatrixUtilTest.java @@ -1,7 +1,8 @@ -package com.thealgorithms.maths; +package com.thealgorithms.matrix; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.thealgorithms.matrix.utils.MatrixUtil; import java.math.BigDecimal; import java.util.Objects; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/thealgorithms/misc/MedianOfMatrixtest.java b/src/test/java/com/thealgorithms/matrix/MedianOfMatrixTest.java similarity index 92% rename from src/test/java/com/thealgorithms/misc/MedianOfMatrixtest.java rename to src/test/java/com/thealgorithms/matrix/MedianOfMatrixTest.java index ec3a84b86c5b..db66bb2d187b 100644 --- a/src/test/java/com/thealgorithms/misc/MedianOfMatrixtest.java +++ b/src/test/java/com/thealgorithms/matrix/MedianOfMatrixTest.java @@ -1,4 +1,4 @@ -package com.thealgorithms.misc; +package com.thealgorithms.matrix; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -7,7 +7,7 @@ import java.util.List; import org.junit.jupiter.api.Test; -public class MedianOfMatrixtest { +public class MedianOfMatrixTest { @Test public void testMedianWithOddNumberOfElements() { diff --git a/src/test/java/com/thealgorithms/matrix/MirrorOfMatrixTest.java b/src/test/java/com/thealgorithms/matrix/MirrorOfMatrixTest.java new file mode 100644 index 000000000000..2e4370922370 --- /dev/null +++ b/src/test/java/com/thealgorithms/matrix/MirrorOfMatrixTest.java @@ -0,0 +1,53 @@ +package com.thealgorithms.matrix; + +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 MirrorOfMatrixTest { + + @Test + void testMirrorMatrixRegularMatrix() { + double[][] originalMatrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; + double[][] expectedMirrorMatrix = {{3, 2, 1}, {6, 5, 4}, {9, 8, 7}}; + double[][] mirroredMatrix = MirrorOfMatrix.mirrorMatrix(originalMatrix); + assertArrayEquals(expectedMirrorMatrix, mirroredMatrix); + } + + @Test + void testMirrorMatrixEmptyMatrix() { + double[][] originalMatrix = {}; + Exception e = assertThrows(IllegalArgumentException.class, () -> MirrorOfMatrix.mirrorMatrix(originalMatrix)); + assertEquals("The input matrix cannot be empty", e.getMessage()); + } + + @Test + void testMirrorMatrixSingleElementMatrix() { + double[][] originalMatrix = {{42}}; + double[][] expectedMirrorMatrix = {{42}}; + double[][] mirroredMatrix = MirrorOfMatrix.mirrorMatrix(originalMatrix); + assertArrayEquals(expectedMirrorMatrix, mirroredMatrix); + } + + @Test + void testMirrorMatrixMultipleRowsOneColumnMatrix() { + double[][] originalMatrix = {{1}, {2}, {3}, {4}}; + double[][] expectedMirrorMatrix = {{1}, {2}, {3}, {4}}; + double[][] mirroredMatrix = MirrorOfMatrix.mirrorMatrix(originalMatrix); + assertArrayEquals(expectedMirrorMatrix, mirroredMatrix); + } + + @Test + void testMirrorMatrixNullInput() { + double[][] originalMatrix = null; + Exception e = assertThrows(IllegalArgumentException.class, () -> MirrorOfMatrix.mirrorMatrix(originalMatrix)); + assertEquals("The input matrix cannot be null", e.getMessage()); + } + + @Test + void testMirrorMatrixThrows() { + assertThrows(IllegalArgumentException.class, () -> MirrorOfMatrix.mirrorMatrix(new double[][] {{1}, {2, 3}})); + } +} diff --git a/src/test/java/com/thealgorithms/matrix/SolveSystemTest.java b/src/test/java/com/thealgorithms/matrix/SolveSystemTest.java new file mode 100644 index 000000000000..c8d289bd8339 --- /dev/null +++ b/src/test/java/com/thealgorithms/matrix/SolveSystemTest.java @@ -0,0 +1,21 @@ +package com.thealgorithms.matrix; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class SolveSystemTest { + + @ParameterizedTest + @MethodSource({"matrixGenerator"}) + void solveSystem(double[][] matrix, double[] constants, double[] solution) { + double[] expected = SolveSystem.solveSystem(matrix, constants); + assertArrayEquals(expected, solution, 1.0E-10, "Solution does not match expected"); + } + private static Stream matrixGenerator() { + return Stream.of(Arguments.of(new double[][] {{-5, 8, -4}, {0, 6, 3}, {0, 0, -4}}, new double[] {38, -9, 20}, new double[] {-2, 1, -5}), Arguments.of(new double[][] {{-2, -1, -1}, {3, 4, 1}, {3, 6, 5}}, new double[] {-11, 19, 43}, new double[] {2, 2, 5})); + } +} diff --git a/src/test/java/com/thealgorithms/matrix/TestPrintMatrixInSpiralOrder.java b/src/test/java/com/thealgorithms/matrix/TestPrintMatrixInSpiralOrder.java new file mode 100644 index 000000000000..bb415a5861a8 --- /dev/null +++ b/src/test/java/com/thealgorithms/matrix/TestPrintMatrixInSpiralOrder.java @@ -0,0 +1,26 @@ +package com.thealgorithms.matrix; + +import static org.junit.jupiter.api.Assertions.assertIterableEquals; + +import java.util.List; +import org.junit.jupiter.api.Test; + +public class TestPrintMatrixInSpiralOrder { + @Test + public void testOne() { + int[][] matrix = {{3, 4, 5, 6, 7}, {8, 9, 10, 11, 12}, {14, 15, 16, 17, 18}, {23, 24, 25, 26, 27}, {30, 31, 32, 33, 34}}; + var printClass = new PrintAMatrixInSpiralOrder(); + List res = printClass.print(matrix, matrix.length, matrix[0].length); + List list = List.of(3, 4, 5, 6, 7, 12, 18, 27, 34, 33, 32, 31, 30, 23, 14, 8, 9, 10, 11, 17, 26, 25, 24, 15, 16); + assertIterableEquals(res, list); + } + + @Test + public void testTwo() { + int[][] matrix = {{2, 2}}; + var printClass = new PrintAMatrixInSpiralOrder(); + List res = printClass.print(matrix, matrix.length, matrix[0].length); + List list = List.of(2, 2); + assertIterableEquals(res, list); + } +} diff --git a/src/test/java/com/thealgorithms/misc/MirrorOfMatrixTest.java b/src/test/java/com/thealgorithms/misc/MirrorOfMatrixTest.java deleted file mode 100644 index 0da0cf0f804a..000000000000 --- a/src/test/java/com/thealgorithms/misc/MirrorOfMatrixTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.thealgorithms.misc; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Test; - -class MirrorOfMatrixTest { - - @Test - void testMirrorMatrixRegularMatrix() { - int[][] originalMatrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; - int[][] expectedMirrorMatrix = {{3, 2, 1}, {6, 5, 4}, {9, 8, 7}}; - int[][] mirroredMatrix = MirrorOfMatrix.mirrorMatrix(originalMatrix); - assertArrayEquals(expectedMirrorMatrix, mirroredMatrix); - } - - @Test - void testMirrorMatrixEmptyMatrix() { - int[][] originalMatrix = {}; - int[][] expectedMirrorMatrix = {}; - int[][] mirroredMatrix = MirrorOfMatrix.mirrorMatrix(originalMatrix); - assertArrayEquals(expectedMirrorMatrix, mirroredMatrix); - } - - @Test - void testMirrorMatrixSingleElementMatrix() { - int[][] originalMatrix = {{42}}; - int[][] expectedMirrorMatrix = {{42}}; - int[][] mirroredMatrix = MirrorOfMatrix.mirrorMatrix(originalMatrix); - assertArrayEquals(expectedMirrorMatrix, mirroredMatrix); - } - - @Test - void testMirrorMatrixMultipleRowsOneColumnMatrix() { - int[][] originalMatrix = {{1}, {2}, {3}, {4}}; - int[][] expectedMirrorMatrix = {{1}, {2}, {3}, {4}}; - int[][] mirroredMatrix = MirrorOfMatrix.mirrorMatrix(originalMatrix); - assertArrayEquals(expectedMirrorMatrix, mirroredMatrix); - } - - @Test - void testMirrorMatrixNullInput() { - int[][] originalMatrix = null; - assertNull(MirrorOfMatrix.mirrorMatrix(originalMatrix)); - } - - @Test - void testMirrotMarixThrows() { - assertThrows(IllegalArgumentException.class, () -> MirrorOfMatrix.mirrorMatrix(new int[][] {{1}, {2, 3}})); - } -} diff --git a/src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java b/src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java new file mode 100644 index 000000000000..130cd19b47b1 --- /dev/null +++ b/src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java @@ -0,0 +1,53 @@ +package com.thealgorithms.misc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.Test; + +public class PalindromePrimeTest { + + @Test + public void testPrimeWithPrimeNumbers() { + assertTrue(PalindromePrime.prime(2), "2 should be prime"); + assertTrue(PalindromePrime.prime(3), "3 should be prime"); + assertTrue(PalindromePrime.prime(5), "5 should be prime"); + assertTrue(PalindromePrime.prime(11), "11 should be prime"); + } + + @Test + public void testPrimeWithNonPrimeNumbers() { + assertFalse(PalindromePrime.prime(1), "1 is not prime"); + assertFalse(PalindromePrime.prime(4), "4 is not prime"); + assertFalse(PalindromePrime.prime(9), "9 is not prime"); + assertFalse(PalindromePrime.prime(15), "15 is not prime"); + } + + @Test + public void testReverse() { + assertEquals(123, PalindromePrime.reverse(321), "Reverse of 321 should be 123"); + assertEquals(7, PalindromePrime.reverse(7), "Reverse of 7 should be 7"); + assertEquals(1221, PalindromePrime.reverse(1221), "Reverse of 1221 should be 1221"); + } + + @Test + public void testGeneratePalindromePrimes() { + List result = PalindromePrime.generatePalindromePrimes(5); + List expected = List.of(2, 3, 5, 7, 11); + assertEquals(expected, result, "The first 5 palindromic primes should be [2, 3, 5, 7, 11]"); + } + + @Test + public void testGeneratePalindromePrimesWithZero() { + List result = PalindromePrime.generatePalindromePrimes(0); + assertTrue(result.isEmpty(), "Generating 0 palindromic primes should return an empty list"); + } + + @Test + public void testGeneratePalindromePrimesWithNegativeInput() { + List result = PalindromePrime.generatePalindromePrimes(-5); + assertTrue(result.isEmpty(), "Generating a negative number of palindromic primes should return an empty list"); + } +} diff --git a/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java b/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java index ae0d6ae0674d..0f0577d39094 100644 --- a/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java +++ b/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java @@ -7,6 +7,8 @@ import org.junit.jupiter.api.Test; public class PalindromeSinglyLinkedListTest { + + // Stack-based tests @Test public void testWithEmptyList() { assertTrue(PalindromeSinglyLinkedList.isPalindrome(new SinglyLinkedList())); @@ -67,4 +69,74 @@ public void testWithListWithEvenLengthNegative() { exampleList.insert(20); assertFalse(PalindromeSinglyLinkedList.isPalindrome(exampleList)); } + + // Optimized approach tests + @Test + public void testOptimisedWithEmptyList() { + assertTrue(PalindromeSinglyLinkedList.isPalindromeOptimised(null)); + } + + @Test + public void testOptimisedWithSingleElement() { + PalindromeSinglyLinkedList.Node node = new PalindromeSinglyLinkedList.Node(100); + assertTrue(PalindromeSinglyLinkedList.isPalindromeOptimised(node)); + } + + @Test + public void testOptimisedWithOddLengthPositive() { + PalindromeSinglyLinkedList.Node node1 = new PalindromeSinglyLinkedList.Node(1); + PalindromeSinglyLinkedList.Node node2 = new PalindromeSinglyLinkedList.Node(2); + PalindromeSinglyLinkedList.Node node3 = new PalindromeSinglyLinkedList.Node(1); + node1.next = node2; + node2.next = node3; + assertTrue(PalindromeSinglyLinkedList.isPalindromeOptimised(node1)); + } + + @Test + public void testOptimisedWithOddLengthPositive2() { + PalindromeSinglyLinkedList.Node node1 = new PalindromeSinglyLinkedList.Node(3); + PalindromeSinglyLinkedList.Node node2 = new PalindromeSinglyLinkedList.Node(2); + PalindromeSinglyLinkedList.Node node3 = new PalindromeSinglyLinkedList.Node(1); + PalindromeSinglyLinkedList.Node node4 = new PalindromeSinglyLinkedList.Node(2); + PalindromeSinglyLinkedList.Node node5 = new PalindromeSinglyLinkedList.Node(3); + node1.next = node2; + node2.next = node3; + node3.next = node4; + node4.next = node5; + assertTrue(PalindromeSinglyLinkedList.isPalindromeOptimised(node1)); + } + + @Test + public void testOptimisedWithEvenLengthPositive() { + PalindromeSinglyLinkedList.Node node1 = new PalindromeSinglyLinkedList.Node(10); + PalindromeSinglyLinkedList.Node node2 = new PalindromeSinglyLinkedList.Node(20); + PalindromeSinglyLinkedList.Node node3 = new PalindromeSinglyLinkedList.Node(20); + PalindromeSinglyLinkedList.Node node4 = new PalindromeSinglyLinkedList.Node(10); + node1.next = node2; + node2.next = node3; + node3.next = node4; + assertTrue(PalindromeSinglyLinkedList.isPalindromeOptimised(node1)); + } + + @Test + public void testOptimisedWithOddLengthNegative() { + PalindromeSinglyLinkedList.Node node1 = new PalindromeSinglyLinkedList.Node(1); + PalindromeSinglyLinkedList.Node node2 = new PalindromeSinglyLinkedList.Node(2); + PalindromeSinglyLinkedList.Node node3 = new PalindromeSinglyLinkedList.Node(2); + node1.next = node2; + node2.next = node3; + assertFalse(PalindromeSinglyLinkedList.isPalindromeOptimised(node1)); + } + + @Test + public void testOptimisedWithEvenLengthNegative() { + PalindromeSinglyLinkedList.Node node1 = new PalindromeSinglyLinkedList.Node(10); + PalindromeSinglyLinkedList.Node node2 = new PalindromeSinglyLinkedList.Node(20); + PalindromeSinglyLinkedList.Node node3 = new PalindromeSinglyLinkedList.Node(20); + PalindromeSinglyLinkedList.Node node4 = new PalindromeSinglyLinkedList.Node(20); + node1.next = node2; + node2.next = node3; + node3.next = node4; + assertFalse(PalindromeSinglyLinkedList.isPalindromeOptimised(node1)); + } } diff --git a/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java b/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java new file mode 100644 index 000000000000..7630d3e78dc7 --- /dev/null +++ b/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java @@ -0,0 +1,35 @@ +package com.thealgorithms.misc; + +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; + +public class RangeInSortedArrayTest { + + @ParameterizedTest(name = "Test case {index}: {3}") + @MethodSource("provideSortedRangeTestCases") + void testSortedRange(int[] nums, int key, int[] expectedRange, String description) { + assertArrayEquals(expectedRange, RangeInSortedArray.sortedRange(nums, key), description); + } + + private static Stream provideSortedRangeTestCases() { + return Stream.of(Arguments.of(new int[] {1, 2, 3, 3, 3, 4, 5}, 3, new int[] {2, 4}, "Range for key 3 with multiple occurrences"), Arguments.of(new int[] {1, 2, 3, 3, 3, 4, 5}, 4, new int[] {5, 5}, "Range for key 4 with single occurrence"), + Arguments.of(new int[] {0, 1, 2}, 3, new int[] {-1, -1}, "Range for non-existent key"), Arguments.of(new int[] {}, 1, new int[] {-1, -1}, "Range in empty array"), Arguments.of(new int[] {1, 1, 1, 2, 3, 4, 5, 5, 5}, 1, new int[] {0, 2}, "Range for key at start"), + Arguments.of(new int[] {1, 1, 1, 2, 3, 4, 5, 5, 5}, 5, new int[] {6, 8}, "Range for key at end")); + } + + @ParameterizedTest(name = "Test case {index}: {3}") + @MethodSource("provideGetCountLessThanTestCases") + void testGetCountLessThan(int[] nums, int key, int expectedCount, String description) { + assertEquals(expectedCount, RangeInSortedArray.getCountLessThan(nums, key), description); + } + + private static Stream provideGetCountLessThanTestCases() { + return Stream.of(Arguments.of(new int[] {1, 2, 3, 3, 4, 5}, 3, 4, "Count of elements less than existing key"), Arguments.of(new int[] {1, 2, 3, 3, 4, 5}, 4, 5, "Count of elements less than non-existing key"), Arguments.of(new int[] {1, 2, 2, 3}, 5, 4, "Count with all smaller elements"), + Arguments.of(new int[] {2, 3, 4, 5}, 1, 0, "Count with no smaller elements"), Arguments.of(new int[] {}, 1, 0, "Count in empty array")); + } +} diff --git a/src/test/java/com/thealgorithms/misc/ShuffleArrayTest.java b/src/test/java/com/thealgorithms/misc/ShuffleArrayTest.java new file mode 100644 index 000000000000..915b83e376b6 --- /dev/null +++ b/src/test/java/com/thealgorithms/misc/ShuffleArrayTest.java @@ -0,0 +1,84 @@ +package com.thealgorithms.misc; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class ShuffleArrayTest { + + @Test + void testShuffleBasic() { + int[] arr = {1, 2, 3, 4, 5}; + int[] originalArr = arr.clone(); // Clone original array for comparison + ShuffleArray.shuffle(arr); + + // Check that the shuffled array is not the same as the original + assertNotEquals(originalArr, arr); + } + + @Test + void testShuffleSingleElement() { + int[] arr = {1}; + int[] originalArr = arr.clone(); + ShuffleArray.shuffle(arr); + + // Check that the shuffled array is the same as the original + assertArrayEquals(originalArr, arr); + } + + @Test + void testShuffleTwoElements() { + int[] arr = {1, 2}; + int[] originalArr = arr.clone(); + ShuffleArray.shuffle(arr); + + // Check that the shuffled array is not the same as the original + assertNotEquals(originalArr, arr); + // Check that the shuffled array still contains the same elements + assertTrue(arr[0] == 1 || arr[0] == 2); + assertTrue(arr[1] == 1 || arr[1] == 2); + } + + @Test + void testShuffleEmptyArray() { + int[] arr = {}; + int[] originalArr = arr.clone(); + ShuffleArray.shuffle(arr); + + // Check that the shuffled array is the same as the original (still empty) + assertArrayEquals(originalArr, arr); + } + + @Test + void testShuffleLargeArray() { + int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + int[] originalArr = arr.clone(); + ShuffleArray.shuffle(arr); + + // Check that the shuffled array is not the same as the original + assertNotEquals(originalArr, arr); + } + + @Test + void testShuffleRetainsElements() { + int[] arr = {1, 2, 3, 4, 5}; + ShuffleArray.shuffle(arr); + + // Check that the shuffled array contains the same elements + assertTrue(arr.length == 5); + for (int i = 1; i <= 5; i++) { + assertTrue(contains(arr, i)); + } + } + + private boolean contains(int[] arr, int value) { + for (int num : arr) { + if (num == value) { + return true; + } + } + return false; + } +} diff --git a/src/test/java/com/thealgorithms/misc/SparsityTest.java b/src/test/java/com/thealgorithms/misc/SparsityTest.java new file mode 100644 index 000000000000..b93e4f44937d --- /dev/null +++ b/src/test/java/com/thealgorithms/misc/SparsityTest.java @@ -0,0 +1,36 @@ +package com.thealgorithms.misc; + +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; + +public class SparsityTest { + + private static final double DELTA = 1e-9; + + @ParameterizedTest(name = "Test case {index}: {2}") + @MethodSource("provideTestCases") + public void testSparsity(double[][] matrix, double expectedSparsity, String description) { + assertEquals(expectedSparsity, Sparsity.sparsity(matrix), DELTA, description); + } + + private static Stream provideTestCases() { + return Stream.of(Arguments.of(new double[][] {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, 1.0, "Matrix with all zero elements"), Arguments.of(new double[][] {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}, 0.0, "Matrix with no zero elements"), + Arguments.of(new double[][] {{0, 2, 0}, {4, 0, 6}, {0, 8, 0}}, 5.0 / 9.0, "Matrix with mixed elements"), Arguments.of(new double[][] {{0, 1, 0, 2, 0}}, 3.0 / 5.0, "Single-row matrix"), Arguments.of(new double[][] {{1}, {0}, {0}, {2}}, 2.0 / 4.0, "Single-column matrix"), + Arguments.of(new double[][] {{0}}, 1.0, "Matrix with a single zero element"), Arguments.of(new double[][] {{5}}, 0.0, "Matrix with a single non-zero element")); + } + + @ParameterizedTest(name = "Test case {index}: {1}") + @MethodSource("provideExceptionTestCases") + public void testSparsityExceptions(double[][] matrix, String description) { + assertThrows(IllegalArgumentException.class, () -> Sparsity.sparsity(matrix), description); + } + + private static Stream provideExceptionTestCases() { + return Stream.of(Arguments.of(new double[][] {}, "Empty matrix should throw IllegalArgumentException")); + } +} diff --git a/src/test/java/com/thealgorithms/misc/ThreeSumProblemTest.java b/src/test/java/com/thealgorithms/misc/ThreeSumProblemTest.java new file mode 100644 index 000000000000..5353168216ec --- /dev/null +++ b/src/test/java/com/thealgorithms/misc/ThreeSumProblemTest.java @@ -0,0 +1,52 @@ +package com.thealgorithms.misc; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class ThreeSumProblemTest { + + private ThreeSumProblem tsp; + + @BeforeEach + public void setup() { + tsp = new ThreeSumProblem(); + } + + @ParameterizedTest + @MethodSource("bruteForceTestProvider") + public void testBruteForce(int[] nums, int target, List> expected) { + assertEquals(expected, tsp.bruteForce(nums, target)); + } + + @ParameterizedTest + @MethodSource("twoPointerTestProvider") + public void testTwoPointer(int[] nums, int target, List> expected) { + assertEquals(expected, tsp.twoPointer(nums, target)); + } + + @ParameterizedTest + @MethodSource("hashMapTestProvider") + public void testHashMap(int[] nums, int target, List> expected) { + assertEquals(expected, tsp.hashMap(nums, target)); + } + + private static Stream bruteForceTestProvider() { + return Stream.of(Arguments.of(new int[] {1, 2, -3, 4, -2, -1}, 0, Arrays.asList(Arrays.asList(-3, 1, 2), Arrays.asList(-3, -1, 4))), Arguments.of(new int[] {1, 2, 3, 4, 5}, 50, new ArrayList<>())); + } + + private static Stream twoPointerTestProvider() { + return Stream.of(Arguments.of(new int[] {0, -1, 2, -3, 1}, 0, Arrays.asList(Arrays.asList(-3, 1, 2), Arrays.asList(-1, 0, 1))), Arguments.of(new int[] {-5, -4, -3, -2, -1}, -10, Arrays.asList(Arrays.asList(-5, -4, -1), Arrays.asList(-5, -3, -2)))); + } + + private static Stream hashMapTestProvider() { + return Stream.of(Arguments.of(new int[] {1, 2, -1, -4, 3, 0}, 2, Arrays.asList(Arrays.asList(-1, 0, 3), Arrays.asList(-1, 1, 2))), Arguments.of(new int[] {5, 7, 9, 11}, 10, new ArrayList<>()), Arguments.of(new int[] {}, 0, new ArrayList<>())); + } +} diff --git a/src/test/java/com/thealgorithms/others/FloydTriangleTest.java b/src/test/java/com/thealgorithms/others/FloydTriangleTest.java index afa280c09838..b336ac4be51f 100644 --- a/src/test/java/com/thealgorithms/others/FloydTriangleTest.java +++ b/src/test/java/com/thealgorithms/others/FloydTriangleTest.java @@ -1,5 +1,7 @@ package com.thealgorithms.others; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.Arrays; @@ -10,37 +12,37 @@ public class FloydTriangleTest { @Test public void testGenerateFloydTriangleWithValidInput() { - List> expectedOutput = Arrays.asList(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6)); + List> expectedOutput = Arrays.asList(singletonList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6)); assertEquals(expectedOutput, FloydTriangle.generateFloydTriangle(3)); } @Test public void testGenerateFloydTriangleWithOneRow() { - List> expectedOutput = Arrays.asList(Arrays.asList(1)); + List> expectedOutput = singletonList(singletonList(1)); assertEquals(expectedOutput, FloydTriangle.generateFloydTriangle(1)); } @Test public void testGenerateFloydTriangleWithZeroRows() { - List> expectedOutput = Arrays.asList(); + List> expectedOutput = emptyList(); assertEquals(expectedOutput, FloydTriangle.generateFloydTriangle(0)); } @Test public void testGenerateFloydTriangleWithNegativeRows() { - List> expectedOutput = Arrays.asList(); + List> expectedOutput = emptyList(); assertEquals(expectedOutput, FloydTriangle.generateFloydTriangle(-3)); } @Test public void testGenerateFloydTriangleWithMultipleRows() { - List> expectedOutput = Arrays.asList(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6), Arrays.asList(7, 8, 9, 10), Arrays.asList(11, 12, 13, 14, 15)); + List> expectedOutput = Arrays.asList(singletonList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6), Arrays.asList(7, 8, 9, 10), Arrays.asList(11, 12, 13, 14, 15)); assertEquals(expectedOutput, FloydTriangle.generateFloydTriangle(5)); } @Test public void testGenerateFloydTriangleWithMoreMultipleRows() { - List> expectedOutput = Arrays.asList(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6), Arrays.asList(7, 8, 9, 10), Arrays.asList(11, 12, 13, 14, 15), Arrays.asList(16, 17, 18, 19, 20, 21), Arrays.asList(22, 23, 24, 25, 26, 27, 28)); + List> expectedOutput = Arrays.asList(singletonList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6), Arrays.asList(7, 8, 9, 10), Arrays.asList(11, 12, 13, 14, 15), Arrays.asList(16, 17, 18, 19, 20, 21), Arrays.asList(22, 23, 24, 25, 26, 27, 28)); assertEquals(expectedOutput, FloydTriangle.generateFloydTriangle(7)); } } diff --git a/src/test/java/com/thealgorithms/others/MaximumSlidingWindowTest.java b/src/test/java/com/thealgorithms/others/MaximumSlidingWindowTest.java new file mode 100644 index 000000000000..9209136a5af3 --- /dev/null +++ b/src/test/java/com/thealgorithms/others/MaximumSlidingWindowTest.java @@ -0,0 +1,63 @@ +package com.thealgorithms.others; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class MaximumSlidingWindowTest { + + MaximumSlidingWindow msw; + int[] nums; + int k; + + @BeforeEach + void setUp() { + msw = new MaximumSlidingWindow(); // Initialize the MaximumSlidingWindow object + } + + // Test for a simple sliding window case + @Test + void testMaxSlidingWindowSimpleCase() { + nums = new int[] {1, 3, -1, -3, 5, 3, 6, 7}; + k = 3; + int[] expected = {3, 3, 5, 5, 6, 7}; + assertArrayEquals(expected, msw.maxSlidingWindow(nums, k)); + } + + // Test when window size is 1 (output should be the array itself) + @Test + void testMaxSlidingWindowWindowSizeOne() { + nums = new int[] {4, 2, 12, 11, -5}; + k = 1; + int[] expected = {4, 2, 12, 11, -5}; + assertArrayEquals(expected, msw.maxSlidingWindow(nums, k)); + } + + // Test when the window size is equal to the array length (output should be a single max element) + @Test + void testMaxSlidingWindowWindowSizeEqualsArrayLength() { + nums = new int[] {4, 2, 12, 11, -5}; + k = nums.length; + int[] expected = {12}; // Maximum of the entire array + assertArrayEquals(expected, msw.maxSlidingWindow(nums, k)); + } + + // Test when the input array is empty + @Test + void testMaxSlidingWindowEmptyArray() { + nums = new int[] {}; + k = 3; + int[] expected = {}; + assertArrayEquals(expected, msw.maxSlidingWindow(nums, k)); + } + + // Test when the window size is larger than the array (should return empty) + @Test + void testMaxSlidingWindowWindowSizeLargerThanArray() { + nums = new int[] {1, 2, 3}; + k = 5; + int[] expected = {}; // Window size is too large, so no result + assertArrayEquals(expected, msw.maxSlidingWindow(nums, k)); + } +} diff --git a/src/test/java/com/thealgorithms/others/TestPrintMatrixInSpiralOrder.java b/src/test/java/com/thealgorithms/others/TestPrintMatrixInSpiralOrder.java index 986e72ea45b5..d35d4bb60c73 100644 --- a/src/test/java/com/thealgorithms/others/TestPrintMatrixInSpiralOrder.java +++ b/src/test/java/com/thealgorithms/others/TestPrintMatrixInSpiralOrder.java @@ -1,26 +1,26 @@ -package com.thealgorithms.others; - -import static org.junit.jupiter.api.Assertions.assertIterableEquals; - -import java.util.List; -import org.junit.jupiter.api.Test; - -public class TestPrintMatrixInSpiralOrder { - @Test - public void testOne() { - int[][] matrix = {{3, 4, 5, 6, 7}, {8, 9, 10, 11, 12}, {14, 15, 16, 17, 18}, {23, 24, 25, 26, 27}, {30, 31, 32, 33, 34}}; - var printClass = new PrintAMatrixInSpiralOrder(); - List res = printClass.print(matrix, matrix.length, matrix[0].length); - List list = List.of(3, 4, 5, 6, 7, 12, 18, 27, 34, 33, 32, 31, 30, 23, 14, 8, 9, 10, 11, 17, 26, 25, 24, 15, 16); - assertIterableEquals(res, list); - } - - @Test - public void testTwo() { - int[][] matrix = {{2, 2}}; - var printClass = new PrintAMatrixInSpiralOrder(); - List res = printClass.print(matrix, matrix.length, matrix[0].length); - List list = List.of(2, 2); - assertIterableEquals(res, list); - } -} +package com.thealgorithms.others; + +import static org.junit.jupiter.api.Assertions.assertIterableEquals; + +import java.util.List; +import org.junit.jupiter.api.Test; + +public class TestPrintMatrixInSpiralOrder { + @Test + public void testOne() { + int[][] matrix = {{3, 4, 5, 6, 7}, {8, 9, 10, 11, 12}, {14, 15, 16, 17, 18}, {23, 24, 25, 26, 27}, {30, 31, 32, 33, 34}}; + var printClass = new PrintAMatrixInSpiralOrder(); + List res = printClass.print(matrix, matrix.length, matrix[0].length); + List list = List.of(3, 4, 5, 6, 7, 12, 18, 27, 34, 33, 32, 31, 30, 23, 14, 8, 9, 10, 11, 17, 26, 25, 24, 15, 16); + assertIterableEquals(res, list); + } + + @Test + public void testTwo() { + int[][] matrix = {{2, 2}}; + var printClass = new PrintAMatrixInSpiralOrder(); + List res = printClass.print(matrix, matrix.length, matrix[0].length); + List list = List.of(2, 2); + assertIterableEquals(res, list); + } +} diff --git a/src/test/java/com/thealgorithms/puzzlesandgames/SudokuTest.java b/src/test/java/com/thealgorithms/puzzlesandgames/SudokuTest.java new file mode 100644 index 000000000000..7fb96dcf805f --- /dev/null +++ b/src/test/java/com/thealgorithms/puzzlesandgames/SudokuTest.java @@ -0,0 +1,38 @@ +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 new file mode 100644 index 000000000000..42669eb03bb4 --- /dev/null +++ b/src/test/java/com/thealgorithms/puzzlesandgames/TowerOfHanoiTest.java @@ -0,0 +1,50 @@ +package com.thealgorithms.puzzlesandgames; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class TowerOfHanoiTest { + + @Test + public void testHanoiWithOneDisc() { + List result = new ArrayList<>(); + TowerOfHanoi.shift(1, "Pole1", "Pole2", "Pole3", result); + + // Expected output for 1 disc + List expected = List.of("Move 1 from Pole1 to Pole3"); + assertEquals(expected, result); + } + + @Test + public void testHanoiWithTwoDiscs() { + List result = new ArrayList<>(); + TowerOfHanoi.shift(2, "Pole1", "Pole2", "Pole3", result); + + // Expected output for 2 discs + List expected = List.of("Move 1 from Pole1 to Pole2", "Move 2 from Pole1 to Pole3", "Move 1 from Pole2 to Pole3"); + assertEquals(expected, result); + } + + @Test + public void testHanoiWithThreeDiscs() { + List result = new ArrayList<>(); + TowerOfHanoi.shift(3, "Pole1", "Pole2", "Pole3", result); + + // Expected output for 3 discs + List expected = List.of("Move 1 from Pole1 to Pole3", "Move 2 from Pole1 to Pole2", "Move 1 from Pole3 to Pole2", "Move 3 from Pole1 to Pole3", "Move 1 from Pole2 to Pole1", "Move 2 from Pole2 to Pole3", "Move 1 from Pole1 to Pole3"); + assertEquals(expected, result); + } + + @Test + public void testHanoiWithZeroDiscs() { + List result = new ArrayList<>(); + TowerOfHanoi.shift(0, "Pole1", "Pole2", "Pole3", result); + + // There should be no moves if there are 0 discs + assertTrue(result.isEmpty()); + } +} diff --git a/src/test/java/com/thealgorithms/puzzlesandgames/WordBoggleTest.java b/src/test/java/com/thealgorithms/puzzlesandgames/WordBoggleTest.java new file mode 100644 index 000000000000..ef5d3c92eb5e --- /dev/null +++ b/src/test/java/com/thealgorithms/puzzlesandgames/WordBoggleTest.java @@ -0,0 +1,57 @@ +package com.thealgorithms.puzzlesandgames; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class WordBoggleTest { + private char[][] board; + + @BeforeEach + void setup() { + board = new char[][] { + {'t', 'h', 'i', 's', 'i', 's', 'a'}, + {'s', 'i', 'm', 'p', 'l', 'e', 'x'}, + {'b', 'x', 'x', 'x', 'x', 'e', 'b'}, + {'x', 'o', 'g', 'g', 'l', 'x', 'o'}, + {'x', 'x', 'x', 'D', 'T', 'r', 'a'}, + {'R', 'E', 'P', 'E', 'A', 'd', 'x'}, + {'x', 'x', 'x', 'x', 'x', 'x', 'x'}, + {'N', 'O', 'T', 'R', 'E', '_', 'P'}, + {'x', 'x', 'D', 'E', 'T', 'A', 'E'}, + }; + } + + @ParameterizedTest + @MethodSource("provideTestCases") + void testBoggleBoard(String[] words, List expectedWords, String testDescription) { + List result = WordBoggle.boggleBoard(board, words); + assertEquals(expectedWords.size(), result.size(), "Test failed for: " + testDescription); + assertTrue(expectedWords.containsAll(result), "Test failed for: " + testDescription); + } + + private static Stream provideTestCases() { + return Stream.of(Arguments.of(new String[] {"this", "is", "not", "a", "simple", "test", "boggle", "board", "REPEATED", "NOTRE_PEATED"}, Arrays.asList("this", "is", "a", "simple", "board", "boggle", "NOTRE_PEATED"), "All words"), + Arguments.of(new String[] {"xyz", "hello", "world"}, List.of(), "No matching words"), Arguments.of(new String[] {}, List.of(), "Empty words array"), Arguments.of(new String[] {"this", "this", "board", "board"}, Arrays.asList("this", "board"), "Duplicate words in input")); + } + + @ParameterizedTest + @MethodSource("provideSpecialCases") + void testBoggleBoardSpecialCases(char[][] specialBoard, String[] words, Collection expectedWords, String testDescription) { + List result = WordBoggle.boggleBoard(specialBoard, words); + assertEquals(expectedWords.size(), result.size(), "Test failed for: " + testDescription); + assertTrue(expectedWords.containsAll(result), "Test failed for: " + testDescription); + } + + private static Stream provideSpecialCases() { + return Stream.of(Arguments.of(new char[0][0], new String[] {"this", "is", "a", "test"}, List.of(), "Empty board")); + } +} diff --git a/src/test/java/com/thealgorithms/randomized/KargerMinCutTest.java b/src/test/java/com/thealgorithms/randomized/KargerMinCutTest.java new file mode 100644 index 000000000000..876b6bf45eaf --- /dev/null +++ b/src/test/java/com/thealgorithms/randomized/KargerMinCutTest.java @@ -0,0 +1,114 @@ +package com.thealgorithms.randomized; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class KargerMinCutTest { + + @Test + public void testSimpleGraph() { + // Graph: 0 -- 1 + Collection nodes = Arrays.asList(0, 1); + List edges = List.of(new int[] {0, 1}); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(1, result.minCut()); + assertTrue(result.first().contains(0) || result.first().contains(1)); + assertTrue(result.second().contains(0) || result.second().contains(1)); + } + + @Test + public void testTriangleGraph() { + // Graph: 0 -- 1 -- 2 -- 0 + Collection nodes = Arrays.asList(0, 1, 2); + List edges = List.of(new int[] {0, 1}, new int[] {1, 2}, new int[] {2, 0}); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(2, result.minCut()); + } + + @Test + public void testSquareGraph() { + // Graph: 0 -- 1 + // | | + // 3 -- 2 + Collection nodes = Arrays.asList(0, 1, 2, 3); + List edges = List.of(new int[] {0, 1}, new int[] {1, 2}, new int[] {2, 3}, new int[] {3, 0}); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(2, result.minCut()); + } + + @Test + public void testDisconnectedGraph() { + // Graph: 0 -- 1 2 -- 3 + Collection nodes = Arrays.asList(0, 1, 2, 3); + List edges = List.of(new int[] {0, 1}, new int[] {2, 3}); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(0, result.minCut()); + } + + @Test + public void testCompleteGraph() { + // Complete Graph: 0 -- 1 -- 2 -- 3 (all nodes connected to each other) + Collection nodes = Arrays.asList(0, 1, 2, 3); + List edges = List.of(new int[] {0, 1}, new int[] {0, 2}, new int[] {0, 3}, new int[] {1, 2}, new int[] {1, 3}, new int[] {2, 3}); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(3, result.minCut()); + } + + @Test + public void testSingleNodeGraph() { + // Graph: Single node with no edges + Collection nodes = List.of(0); + List edges = List.of(); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(0, result.minCut()); + assertTrue(result.first().contains(0)); + assertTrue(result.second().isEmpty()); + } + + @Test + public void testTwoNodesNoEdge() { + // Graph: 0 1 (no edges) + Collection nodes = Arrays.asList(0, 1); + List edges = List.of(); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(0, result.minCut()); + assertTrue(result.first().contains(0) || result.first().contains(1)); + assertTrue(result.second().contains(0) || result.second().contains(1)); + } + + @Test + public void testComplexGraph() { + // Nodes: 0, 1, 2, 3, 4, 5, 6, 7, 8 + // Edges: Fully connected graph with additional edges for complexity + Collection nodes = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8); + List edges = List.of(new int[] {0, 1}, new int[] {0, 2}, new int[] {0, 3}, new int[] {0, 4}, new int[] {0, 5}, new int[] {1, 2}, new int[] {1, 3}, new int[] {1, 4}, new int[] {1, 5}, new int[] {1, 6}, new int[] {2, 3}, new int[] {2, 4}, new int[] {2, 5}, new int[] {2, 6}, + new int[] {2, 7}, new int[] {3, 4}, new int[] {3, 5}, new int[] {3, 6}, new int[] {3, 7}, new int[] {3, 8}, new int[] {4, 5}, new int[] {4, 6}, new int[] {4, 7}, new int[] {4, 8}, new int[] {5, 6}, new int[] {5, 7}, new int[] {5, 8}, new int[] {6, 7}, new int[] {6, 8}, new int[] {7, 8}, + new int[] {0, 6}, new int[] {1, 7}, new int[] {2, 8}); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + // The exact minimum cut value depends on the randomization, but it should be consistent + // for this graph structure. For a fully connected graph, the minimum cut is typically + // determined by the smallest number of edges connecting two partitions. + assertTrue(result.minCut() > 0); + } +} diff --git a/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java b/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java new file mode 100644 index 000000000000..2a3a84b5ceea --- /dev/null +++ b/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java @@ -0,0 +1,91 @@ +package com.thealgorithms.randomized; + +import static com.thealgorithms.randomized.MonteCarloIntegration.approximate; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.function.Function; +import org.junit.jupiter.api.Test; + +class MonteCarloIntegrationTest { + + private static final double EPSILON = 0.03; // Allow 3% error margin + + @Test + void testConstantFunction() { + // Integral of f(x) = 2 from 0 to 1 is 2 + Function constant = x -> 2.0; + double result = approximate(constant, 0, 1, 10000); + assertEquals(2.0, result, EPSILON); + } + + @Test + void testLinearFunction() { + // Integral of f(x) = x from 0 to 1 is 0.5 + Function linear = Function.identity(); + double result = approximate(linear, 0, 1, 10000); + assertEquals(0.5, result, EPSILON); + } + + @Test + void testQuadraticFunction() { + // Integral of f(x) = x^2 from 0 to 1 is 1/3 + Function quadratic = x -> x * x; + double result = approximate(quadratic, 0, 1, 10000); + assertEquals(1.0 / 3.0, result, EPSILON); + } + + @Test + void testLargeSampleSize() { + // Integral of f(x) = x^2 from 0 to 1 is 1/3 + Function quadratic = x -> x * x; + double result = approximate(quadratic, 0, 1, 50000000); + assertEquals(1.0 / 3.0, result, EPSILON / 2); // Larger sample size, smaller error margin + } + + @Test + void testReproducibility() { + Function linear = Function.identity(); + double result1 = approximate(linear, 0, 1, 10000, 42L); + double result2 = approximate(linear, 0, 1, 10000, 42L); + assertEquals(result1, result2, 0.0); // Exactly equal + } + + @Test + void testNegativeInterval() { + // Integral of f(x) = x from -1 to 1 is 0 + Function linear = Function.identity(); + double result = approximate(linear, -1, 1, 10000); + assertEquals(0.0, result, EPSILON); + } + + @Test + void testNullFunction() { + Exception exception = assertThrows(IllegalArgumentException.class, () -> approximate(null, 0, 1, 1000)); + assertNotNull(exception); + } + + @Test + void testInvalidInterval() { + Function linear = Function.identity(); + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + approximate(linear, 2, 1, 1000); // b <= a + }); + assertNotNull(exception); + } + + @Test + void testZeroSampleSize() { + Function linear = Function.identity(); + Exception exception = assertThrows(IllegalArgumentException.class, () -> approximate(linear, 0, 1, 0)); + assertNotNull(exception); + } + + @Test + void testNegativeSampleSize() { + Function linear = Function.identity(); + Exception exception = assertThrows(IllegalArgumentException.class, () -> approximate(linear, 0, 1, -100)); + assertNotNull(exception); + } +} diff --git a/src/test/java/com/thealgorithms/randomized/RandomizedQuickSortTest.java b/src/test/java/com/thealgorithms/randomized/RandomizedQuickSortTest.java new file mode 100644 index 000000000000..ec3d5a0b3546 --- /dev/null +++ b/src/test/java/com/thealgorithms/randomized/RandomizedQuickSortTest.java @@ -0,0 +1,44 @@ +package com.thealgorithms.randomized; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the RandomizedQuickSort class. + */ +public class RandomizedQuickSortTest { + + /** + * Tests sorting of an array with multiple elements, including duplicates. + */ + @Test + public void testRandomizedQuickSortMultipleElements() { + int[] arr = {3, 6, 8, 10, 1, 2, 1}; + int[] expected = {1, 1, 2, 3, 6, 8, 10}; + RandomizedQuickSort.randomizedQuickSort(arr, 0, arr.length - 1); + assertArrayEquals(expected, arr); + } + + /** + * Tests sorting of an empty array. + */ + @Test + public void testRandomizedQuickSortEmptyArray() { + int[] arr = {}; + int[] expected = {}; + RandomizedQuickSort.randomizedQuickSort(arr, 0, arr.length - 1); + assertArrayEquals(expected, arr); + } + + /** + * Tests sorting of an array with a single element. + */ + @Test + public void testRandomizedQuickSortSingleElement() { + int[] arr = {5}; + int[] expected = {5}; + RandomizedQuickSort.randomizedQuickSort(arr, 0, arr.length - 1); + assertArrayEquals(expected, arr); + } +} diff --git a/src/test/java/com/thealgorithms/randomized/ReservoirSamplingTest.java b/src/test/java/com/thealgorithms/randomized/ReservoirSamplingTest.java new file mode 100644 index 000000000000..0c6061fcde2a --- /dev/null +++ b/src/test/java/com/thealgorithms/randomized/ReservoirSamplingTest.java @@ -0,0 +1,45 @@ +package com.thealgorithms.randomized; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class ReservoirSamplingTest { + + @Test + public void testSampleSizeEqualsStreamLength() { + int[] stream = {1, 2, 3, 4, 5}; + int sampleSize = 5; + + List result = ReservoirSampling.sample(stream, sampleSize); + + assertEquals(sampleSize, result.size()); + assertTrue(Arrays.stream(stream).allMatch(result::contains)); + } + + @Test + public void testSampleSizeLessThanStreamLength() { + int[] stream = {10, 20, 30, 40, 50, 60}; + int sampleSize = 3; + + List result = ReservoirSampling.sample(stream, sampleSize); + + assertEquals(sampleSize, result.size()); + for (int value : result) { + assertTrue(Arrays.stream(stream).anyMatch(x -> x == value)); + } + } + + @Test + public void testSampleSizeGreaterThanStreamLengthThrowsException() { + int[] stream = {1, 2, 3}; + + Exception exception = assertThrows(IllegalArgumentException.class, () -> ReservoirSampling.sample(stream, 5)); + + assertEquals("Sample size cannot exceed stream size.", exception.getMessage()); + } +} diff --git a/src/test/java/com/thealgorithms/recursion/FibonacciSeriesTest.java b/src/test/java/com/thealgorithms/recursion/FibonacciSeriesTest.java new file mode 100644 index 000000000000..f8b59f7e9ac6 --- /dev/null +++ b/src/test/java/com/thealgorithms/recursion/FibonacciSeriesTest.java @@ -0,0 +1,27 @@ +package com.thealgorithms.recursion; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class FibonacciSeriesTest { + + @Test + public void testFibonacci() { + assertEquals(0, FibonacciSeries.fibonacci(0)); + assertEquals(1, FibonacciSeries.fibonacci(1)); + assertEquals(1, FibonacciSeries.fibonacci(2)); + assertEquals(2, FibonacciSeries.fibonacci(3)); + assertEquals(3, FibonacciSeries.fibonacci(4)); + assertEquals(5, FibonacciSeries.fibonacci(5)); + assertEquals(8, FibonacciSeries.fibonacci(6)); + assertEquals(13, FibonacciSeries.fibonacci(7)); + assertEquals(21, FibonacciSeries.fibonacci(8)); + assertEquals(34, FibonacciSeries.fibonacci(9)); + assertEquals(55, FibonacciSeries.fibonacci(10)); + assertEquals(89, FibonacciSeries.fibonacci(11)); + assertEquals(144, FibonacciSeries.fibonacci(12)); + assertEquals(233, FibonacciSeries.fibonacci(13)); + assertEquals(377, FibonacciSeries.fibonacci(14)); + } +} diff --git a/src/test/java/com/thealgorithms/Recursion/GenerateSubsetsTest.java b/src/test/java/com/thealgorithms/recursion/GenerateSubsetsTest.java similarity index 96% rename from src/test/java/com/thealgorithms/Recursion/GenerateSubsetsTest.java rename to src/test/java/com/thealgorithms/recursion/GenerateSubsetsTest.java index d4bc7e488f80..b92d1406b0a7 100644 --- a/src/test/java/com/thealgorithms/Recursion/GenerateSubsetsTest.java +++ b/src/test/java/com/thealgorithms/recursion/GenerateSubsetsTest.java @@ -1,4 +1,4 @@ -package com.thealgorithms.Recursion; +package com.thealgorithms.recursion; import static org.junit.jupiter.api.Assertions.assertArrayEquals; diff --git a/src/test/java/com/thealgorithms/scheduling/AgingSchedulingTest.java b/src/test/java/com/thealgorithms/scheduling/AgingSchedulingTest.java new file mode 100644 index 000000000000..cab78e4d1c58 --- /dev/null +++ b/src/test/java/com/thealgorithms/scheduling/AgingSchedulingTest.java @@ -0,0 +1,54 @@ +package com.thealgorithms.scheduling; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class AgingSchedulingTest { + + private AgingScheduling scheduler; + + @BeforeEach + public void setup() { + scheduler = new AgingScheduling(); + } + + @Test + public void testAddAndScheduleSingleTask() { + scheduler.addTask("Task1", 5); + assertEquals("Task1", scheduler.scheduleNext()); + } + + @Test + public void testAddMultipleTasks() { + scheduler.addTask("Task1", 1); + scheduler.addTask("Task2", 1); + assertEquals("Task1", scheduler.scheduleNext()); + assertEquals("Task2", scheduler.scheduleNext()); + } + + @Test + public void testPriorityAdjustmentWithWait() { + scheduler.addTask("Task1", 1); + scheduler.addTask("Task2", 1); + scheduler.scheduleNext(); + scheduler.scheduleNext(); + assertEquals("Task1", scheduler.scheduleNext()); + } + + @Test + public void testEmptyScheduler() { + assertNull(scheduler.scheduleNext()); + } + + @Test + public void testMultipleRounds() { + scheduler.addTask("Task1", 1); + scheduler.addTask("Task2", 2); + scheduler.scheduleNext(); + scheduler.scheduleNext(); + assertEquals("Task1", scheduler.scheduleNext()); + } +} diff --git a/src/test/java/com/thealgorithms/scheduling/EDFSchedulingTest.java b/src/test/java/com/thealgorithms/scheduling/EDFSchedulingTest.java new file mode 100644 index 000000000000..8ce290cb246f --- /dev/null +++ b/src/test/java/com/thealgorithms/scheduling/EDFSchedulingTest.java @@ -0,0 +1,69 @@ +package com.thealgorithms.scheduling; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class EDFSchedulingTest { + + private List processes; + + @BeforeEach + public void setup() { + processes = createProcesses(); + } + + @Test + public void testEDFScheduling() { + EDFScheduling edfScheduling = new EDFScheduling(processes); + List executedProcesses = edfScheduling.scheduleProcesses(); + + // Assert the correct number of processes + assertEquals(3, executedProcesses.size()); + + // Assert that processes are executed in order of earliest deadline first + EDFScheduling.Process process1 = executedProcesses.get(0); + assertEquals("P2", process1.getProcessId()); + assertEquals(0, process1.getWaitingTime()); + assertEquals(3, process1.getTurnAroundTime()); + + EDFScheduling.Process process2 = executedProcesses.get(1); + assertEquals("P1", process2.getProcessId()); + assertEquals(3, process2.getWaitingTime()); + assertEquals(10, process2.getTurnAroundTime()); + + EDFScheduling.Process process3 = executedProcesses.get(2); + assertEquals("P3", process3.getProcessId()); + assertEquals(10, process3.getWaitingTime()); + assertEquals(18, process3.getTurnAroundTime()); + } + + @Test + public void testProcessMissedDeadline() { + // Modify the deadline of a process to ensure it will miss its deadline + processes.get(1).setTurnAroundTime(5); // Set P1's deadline to 5 (which it will miss) + + EDFScheduling edfScheduling = new EDFScheduling(processes); + edfScheduling.scheduleProcesses(); + + // Check if the process with ID "P1" missed its deadline + assertEquals("P1", processes.get(1).getProcessId()); + } + + private List createProcesses() { + // Process ID, Burst Time, Deadline + EDFScheduling.Process process1 = new EDFScheduling.Process("P1", 7, 10); // 7 burst time, 10 deadline + EDFScheduling.Process process2 = new EDFScheduling.Process("P2", 3, 5); // 3 burst time, 5 deadline + EDFScheduling.Process process3 = new EDFScheduling.Process("P3", 8, 18); // 8 burst time, 18 deadline + + List processes = new ArrayList<>(); + processes.add(process1); + processes.add(process2); + processes.add(process3); + + return processes; + } +} diff --git a/src/test/java/com/thealgorithms/scheduling/FairShareSchedulingTest.java b/src/test/java/com/thealgorithms/scheduling/FairShareSchedulingTest.java new file mode 100644 index 000000000000..7aa6e061c497 --- /dev/null +++ b/src/test/java/com/thealgorithms/scheduling/FairShareSchedulingTest.java @@ -0,0 +1,60 @@ +package com.thealgorithms.scheduling; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class FairShareSchedulingTest { + + private FairShareScheduling scheduler; + + @BeforeEach + public void setup() { + scheduler = new FairShareScheduling(); + } + + @Test + public void testAllocateResourcesSingleUser() { + scheduler.addUser("User1"); + scheduler.addTask("User1", 5); + scheduler.allocateResources(100); + Map expected = Map.of("User1", 100); + assertEquals(expected, scheduler.getAllocatedResources()); + } + + @Test + public void testAllocateResourcesMultipleUsers() { + scheduler.addUser("User1"); + scheduler.addUser("User2"); + scheduler.addUser("User3"); + scheduler.addTask("User1", 2); + scheduler.addTask("User2", 3); + scheduler.addTask("User3", 5); + scheduler.allocateResources(100); + Map expected = Map.of("User1", 20, "User2", 30, "User3", 50); + assertEquals(expected, scheduler.getAllocatedResources()); + } + + @Test + public void testAllocateResourcesZeroWeightUser() { + scheduler.addUser("User1"); + scheduler.addUser("User2"); + scheduler.addTask("User2", 5); + scheduler.allocateResources(100); + Map expected = Map.of("User1", 0, "User2", 100); + assertEquals(expected, scheduler.getAllocatedResources()); + } + + @Test + public void testAllocateResourcesEqualWeights() { + scheduler.addUser("User1"); + scheduler.addUser("User2"); + scheduler.addTask("User1", 1); + scheduler.addTask("User2", 1); + scheduler.allocateResources(100); + Map expected = Map.of("User1", 50, "User2", 50); + assertEquals(expected, scheduler.getAllocatedResources()); + } +} diff --git a/src/test/java/com/thealgorithms/scheduling/GangSchedulingTest.java b/src/test/java/com/thealgorithms/scheduling/GangSchedulingTest.java new file mode 100644 index 000000000000..2e1bb4cd0e20 --- /dev/null +++ b/src/test/java/com/thealgorithms/scheduling/GangSchedulingTest.java @@ -0,0 +1,52 @@ +package com.thealgorithms.scheduling; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class GangSchedulingTest { + + private GangScheduling scheduler; + + @BeforeEach + public void setup() { + scheduler = new GangScheduling(); + } + + @Test + public void testAddGangAndTask() { + scheduler.addGang("Gang1"); + scheduler.addTaskToGang("Gang1", "Task1"); + Map> expected = Map.of("Gang1", List.of("Task1")); + assertEquals(expected, scheduler.getGangSchedules()); + } + + @Test + public void testMultipleGangs() { + scheduler.addGang("Gang1"); + scheduler.addGang("Gang2"); + scheduler.addTaskToGang("Gang1", "Task1"); + scheduler.addTaskToGang("Gang2", "Task2"); + Map> expected = Map.of("Gang1", List.of("Task1"), "Gang2", List.of("Task2")); + assertEquals(expected, scheduler.getGangSchedules()); + } + + @Test + public void testGangWithMultipleTasks() { + scheduler.addGang("Gang1"); + scheduler.addTaskToGang("Gang1", "Task1"); + scheduler.addTaskToGang("Gang1", "Task2"); + Map> expected = Map.of("Gang1", List.of("Task1", "Task2")); + assertEquals(expected, scheduler.getGangSchedules()); + } + + @Test + public void testEmptyGangSchedule() { + scheduler.addGang("Gang1"); + Map> expected = Map.of("Gang1", List.of()); + assertEquals(expected, scheduler.getGangSchedules()); + } +} diff --git a/src/test/java/com/thealgorithms/scheduling/JobSchedulingWithDeadlineTest.java b/src/test/java/com/thealgorithms/scheduling/JobSchedulingWithDeadlineTest.java new file mode 100644 index 000000000000..538db92a1f26 --- /dev/null +++ b/src/test/java/com/thealgorithms/scheduling/JobSchedulingWithDeadlineTest.java @@ -0,0 +1,43 @@ +package com.thealgorithms.scheduling; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.Test; + +class JobSchedulingWithDeadlineTest { + + @Test + void testJobSequencingWithDeadlines1() { + JobSchedulingWithDeadline.Job[] jobs = {new JobSchedulingWithDeadline.Job(1, 1, 4, 20), new JobSchedulingWithDeadline.Job(2, 1, 1, 10), new JobSchedulingWithDeadline.Job(3, 1, 1, 40), new JobSchedulingWithDeadline.Job(4, 1, 1, 30)}; + int[] result = JobSchedulingWithDeadline.jobSequencingWithDeadlines(jobs); + assertArrayEquals(new int[] {2, 60}, result); // Expected output: 2 jobs, 60 profit + } + + @Test + void testJobSequencingWithDeadlines2() { + JobSchedulingWithDeadline.Job[] jobs = {new JobSchedulingWithDeadline.Job(1, 1, 2, 100), new JobSchedulingWithDeadline.Job(2, 1, 1, 19), new JobSchedulingWithDeadline.Job(3, 1, 2, 27), new JobSchedulingWithDeadline.Job(4, 1, 1, 25), new JobSchedulingWithDeadline.Job(5, 1, 1, 15)}; + int[] result = JobSchedulingWithDeadline.jobSequencingWithDeadlines(jobs); + assertArrayEquals(new int[] {2, 127}, result); // Expected output: 2 jobs, 127 profit + } + + @Test + void testJobSequencingWithDeadlinesWithArrivalTimes() { + JobSchedulingWithDeadline.Job[] jobs = {new JobSchedulingWithDeadline.Job(1, 2, 5, 50), new JobSchedulingWithDeadline.Job(2, 3, 4, 60), new JobSchedulingWithDeadline.Job(3, 1, 3, 20)}; + int[] result = JobSchedulingWithDeadline.jobSequencingWithDeadlines(jobs); + assertArrayEquals(new int[] {3, 130}, result); // All 3 jobs fit within their deadlines + } + + @Test + void testJobSequencingWithDeadlinesNoJobs() { + JobSchedulingWithDeadline.Job[] jobs = {}; + int[] result = JobSchedulingWithDeadline.jobSequencingWithDeadlines(jobs); + assertArrayEquals(new int[] {0, 0}, result); // No jobs, 0 profit + } + + @Test + void testJobSequencingWithDeadlinesSingleJob() { + JobSchedulingWithDeadline.Job[] jobs = {new JobSchedulingWithDeadline.Job(1, 1, 1, 50)}; + int[] result = JobSchedulingWithDeadline.jobSequencingWithDeadlines(jobs); + assertArrayEquals(new int[] {1, 50}, result); // 1 job scheduled, 50 profit + } +} diff --git a/src/test/java/com/thealgorithms/scheduling/LotterySchedulingTest.java b/src/test/java/com/thealgorithms/scheduling/LotterySchedulingTest.java new file mode 100644 index 000000000000..00fd8adcde27 --- /dev/null +++ b/src/test/java/com/thealgorithms/scheduling/LotterySchedulingTest.java @@ -0,0 +1,64 @@ +package com.thealgorithms.scheduling; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class LotterySchedulingTest { + + private Random mockRandom; + + @BeforeEach + public void setup() { + mockRandom = mock(Random.class); + } + + @Test + public void testLotterySchedulingWithMockedRandom() { + List processes = createProcesses(); + LotteryScheduling lotteryScheduling = new LotteryScheduling(processes, mockRandom); + + // Mock the sequence of random numbers (winning tickets) + // This sequence ensures that P1 (10 tickets), P3 (8 tickets), and P2 (5 tickets) are selected. + when(mockRandom.nextInt(23)).thenReturn(5, 18, 11); // winning tickets for P1, P3, and P2 + + List executedProcesses = lotteryScheduling.scheduleProcesses(); + + assertEquals(3, executedProcesses.size()); + + // Assert the process execution order and properties + LotteryScheduling.Process process1 = executedProcesses.get(0); + assertEquals("P1", process1.getProcessId()); + assertEquals(0, process1.getWaitingTime()); + assertEquals(10, process1.getTurnAroundTime()); + + LotteryScheduling.Process process2 = executedProcesses.get(1); + assertEquals("P2", process2.getProcessId()); + assertEquals(10, process2.getWaitingTime()); + assertEquals(15, process2.getTurnAroundTime()); + + LotteryScheduling.Process process3 = executedProcesses.get(2); + assertEquals("P3", process3.getProcessId()); + assertEquals(15, process3.getWaitingTime()); + assertEquals(23, process3.getTurnAroundTime()); + } + + private List createProcesses() { + LotteryScheduling.Process process1 = new LotteryScheduling.Process("P1", 10, 10); // 10 tickets + LotteryScheduling.Process process2 = new LotteryScheduling.Process("P2", 5, 5); // 5 tickets + LotteryScheduling.Process process3 = new LotteryScheduling.Process("P3", 8, 8); // 8 tickets + + List processes = new ArrayList<>(); + processes.add(process1); + processes.add(process2); + processes.add(process3); + + return processes; + } +} diff --git a/src/test/java/com/thealgorithms/scheduling/MultiAgentSchedulingTest.java b/src/test/java/com/thealgorithms/scheduling/MultiAgentSchedulingTest.java new file mode 100644 index 000000000000..16067fa8c22a --- /dev/null +++ b/src/test/java/com/thealgorithms/scheduling/MultiAgentSchedulingTest.java @@ -0,0 +1,52 @@ +package com.thealgorithms.scheduling; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class MultiAgentSchedulingTest { + + private MultiAgentScheduling scheduler; + + @BeforeEach + public void setup() { + scheduler = new MultiAgentScheduling(); + } + + @Test + public void testAddAgentAndAssignTask() { + scheduler.addAgent("Agent1"); + scheduler.assignTask("Agent1", "Task1"); + Map> expected = Map.of("Agent1", List.of("Task1")); + assertEquals(expected, scheduler.getScheduledTasks()); + } + + @Test + public void testMultipleAgentsWithTasks() { + scheduler.addAgent("Agent1"); + scheduler.addAgent("Agent2"); + scheduler.assignTask("Agent1", "Task1"); + scheduler.assignTask("Agent2", "Task2"); + Map> expected = Map.of("Agent1", List.of("Task1"), "Agent2", List.of("Task2")); + assertEquals(expected, scheduler.getScheduledTasks()); + } + + @Test + public void testAgentWithMultipleTasks() { + scheduler.addAgent("Agent1"); + scheduler.assignTask("Agent1", "Task1"); + scheduler.assignTask("Agent1", "Task2"); + Map> expected = Map.of("Agent1", List.of("Task1", "Task2")); + assertEquals(expected, scheduler.getScheduledTasks()); + } + + @Test + public void testEmptyAgentSchedule() { + scheduler.addAgent("Agent1"); + Map> expected = Map.of("Agent1", List.of()); + assertEquals(expected, scheduler.getScheduledTasks()); + } +} diff --git a/src/test/java/com/thealgorithms/scheduling/NonPreemptivePrioritySchedulingTest.java b/src/test/java/com/thealgorithms/scheduling/NonPreemptivePrioritySchedulingTest.java new file mode 100644 index 000000000000..d28dcfeaaea3 --- /dev/null +++ b/src/test/java/com/thealgorithms/scheduling/NonPreemptivePrioritySchedulingTest.java @@ -0,0 +1,70 @@ +package com.thealgorithms.scheduling; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class NonPreemptivePrioritySchedulingTest { + + @Test + public void testCalculateAverageWaitingTime() { + NonPreemptivePriorityScheduling.Process[] processes = {new NonPreemptivePriorityScheduling.Process(1, 0, 10, 2), // id, arrivalTime, burstTime, priority + new NonPreemptivePriorityScheduling.Process(2, 0, 5, 1), new NonPreemptivePriorityScheduling.Process(3, 0, 8, 3)}; + NonPreemptivePriorityScheduling.Process[] executionOrder = NonPreemptivePriorityScheduling.scheduleProcesses(processes); + + double expectedAvgWaitingTime = (0 + 5 + 15) / 3.0; // Waiting times: 0 for P2, 5 for P1, 15 for P3 + double actualAvgWaitingTime = NonPreemptivePriorityScheduling.calculateAverageWaitingTime(processes, executionOrder); + + assertEquals(expectedAvgWaitingTime, actualAvgWaitingTime, 0.01, "Average waiting time should be calculated correctly."); + } + + @Test + public void testCalculateAverageTurnaroundTime() { + NonPreemptivePriorityScheduling.Process[] processes = {new NonPreemptivePriorityScheduling.Process(1, 0, 10, 2), // id, arrivalTime, burstTime, priority + new NonPreemptivePriorityScheduling.Process(2, 0, 5, 1), new NonPreemptivePriorityScheduling.Process(3, 0, 8, 3)}; + + NonPreemptivePriorityScheduling.Process[] executionOrder = NonPreemptivePriorityScheduling.scheduleProcesses(processes); + + double expectedAvgTurnaroundTime = (5 + 15 + 23) / 3.0; // Turnaround times: 5 for P2, 15 for P1, 23 for P3 + double actualAvgTurnaroundTime = NonPreemptivePriorityScheduling.calculateAverageTurnaroundTime(processes, executionOrder); + + assertEquals(expectedAvgTurnaroundTime, actualAvgTurnaroundTime, 0.01, "Average turnaround time should be calculated correctly."); + } + + @Test + public void testStartTimeIsCorrect() { + NonPreemptivePriorityScheduling.Process[] processes = {new NonPreemptivePriorityScheduling.Process(1, 0, 10, 2), // id, arrivalTime, burstTime, priority + new NonPreemptivePriorityScheduling.Process(2, 0, 5, 1), new NonPreemptivePriorityScheduling.Process(3, 0, 8, 3)}; + NonPreemptivePriorityScheduling.Process[] executionOrder = NonPreemptivePriorityScheduling.scheduleProcesses(processes); + + // Check that the start time for each process is correctly set + assertEquals(0, executionOrder[0].startTime, "First process (P2) should start at time 0."); // Process 2 has the highest priority + assertEquals(5, executionOrder[1].startTime, "Second process (P1) should start at time 5."); + assertEquals(15, executionOrder[2].startTime, "Third process (P3) should start at time 15."); + } + + @Test + public void testWithDelayedArrivalTimes() { + NonPreemptivePriorityScheduling.Process[] processes = {new NonPreemptivePriorityScheduling.Process(1, 0, 4, 1), // id, arrivalTime, burstTime, priority + new NonPreemptivePriorityScheduling.Process(2, 2, 3, 2), new NonPreemptivePriorityScheduling.Process(3, 4, 2, 3)}; + NonPreemptivePriorityScheduling.Process[] executionOrder = NonPreemptivePriorityScheduling.scheduleProcesses(processes); + + // Test the start times considering delayed arrivals + assertEquals(0, executionOrder[0].startTime, "First process (P1) should start at time 0."); + assertEquals(4, executionOrder[1].startTime, "Second process (P2) should start at time 4."); // After P1 finishes + assertEquals(7, executionOrder[2].startTime, "Third process (P3) should start at time 7."); // After P2 finishes + } + + @Test + public void testWithGapsInArrivals() { + NonPreemptivePriorityScheduling.Process[] processes = {new NonPreemptivePriorityScheduling.Process(1, 0, 6, 2), // id, arrivalTime, burstTime, priority + new NonPreemptivePriorityScheduling.Process(2, 8, 4, 1), new NonPreemptivePriorityScheduling.Process(3, 12, 5, 3)}; + + NonPreemptivePriorityScheduling.Process[] executionOrder = NonPreemptivePriorityScheduling.scheduleProcesses(processes); + + // Test the start times for processes with gaps in arrival times + assertEquals(0, executionOrder[0].startTime, "First process (P1) should start at time 0."); + assertEquals(8, executionOrder[1].startTime, "Second process (P2) should start at time 8."); // After P1 finishes, arrives at 8 + assertEquals(12, executionOrder[2].startTime, "Third process (P3) should start at time 12."); // After P2 finishes, arrives at 12 + } +} diff --git a/src/test/java/com/thealgorithms/scheduling/PreemptivePrioritySchedulingTest.java b/src/test/java/com/thealgorithms/scheduling/PreemptivePrioritySchedulingTest.java index d0005dda9097..ea692686afd2 100644 --- a/src/test/java/com/thealgorithms/scheduling/PreemptivePrioritySchedulingTest.java +++ b/src/test/java/com/thealgorithms/scheduling/PreemptivePrioritySchedulingTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import com.thealgorithms.devutils.entities.ProcessDetails; +import java.util.Collection; import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; @@ -17,7 +18,7 @@ class PreemptivePrioritySchedulingTest { @ParameterizedTest @MethodSource("provideProcessesAndExpectedSchedules") - void testPreemptivePriorityScheduling(List processes, List expectedSchedule) { + void testPreemptivePriorityScheduling(Collection processes, List expectedSchedule) { PreemptivePriorityScheduling scheduler = new PreemptivePriorityScheduling(processes); scheduler.scheduleProcesses(); assertEquals(expectedSchedule, scheduler.ganttChart); diff --git a/src/test/java/com/thealgorithms/scheduling/ProportionalFairSchedulingTest.java b/src/test/java/com/thealgorithms/scheduling/ProportionalFairSchedulingTest.java new file mode 100644 index 000000000000..0d379ee90b0e --- /dev/null +++ b/src/test/java/com/thealgorithms/scheduling/ProportionalFairSchedulingTest.java @@ -0,0 +1,53 @@ +package com.thealgorithms.scheduling; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class ProportionalFairSchedulingTest { + + private ProportionalFairScheduling scheduler; + + @BeforeEach + public void setup() { + scheduler = new ProportionalFairScheduling(); + } + + @Test + public void testAllocateResourcesSingleProcess() { + scheduler.addProcess("Process1", 5); + scheduler.allocateResources(100); + List expected = List.of("Process1: 100"); + assertEquals(expected, scheduler.getAllocatedResources()); + } + + @Test + public void testAllocateResourcesMultipleProcesses() { + scheduler.addProcess("Process1", 2); + scheduler.addProcess("Process2", 3); + scheduler.addProcess("Process3", 5); + scheduler.allocateResources(100); + List expected = List.of("Process1: 20", "Process2: 30", "Process3: 50"); + assertEquals(expected, scheduler.getAllocatedResources()); + } + + @Test + public void testAllocateResourcesZeroWeightProcess() { + scheduler.addProcess("Process1", 0); + scheduler.addProcess("Process2", 5); + scheduler.allocateResources(100); + List expected = List.of("Process1: 0", "Process2: 100"); + assertEquals(expected, scheduler.getAllocatedResources()); + } + + @Test + public void testAllocateResourcesEqualWeights() { + scheduler.addProcess("Process1", 1); + scheduler.addProcess("Process2", 1); + scheduler.allocateResources(100); + List expected = List.of("Process1: 50", "Process2: 50"); + assertEquals(expected, scheduler.getAllocatedResources()); + } +} diff --git a/src/test/java/com/thealgorithms/scheduling/RandomSchedulingTest.java b/src/test/java/com/thealgorithms/scheduling/RandomSchedulingTest.java new file mode 100644 index 000000000000..e2c8777d892f --- /dev/null +++ b/src/test/java/com/thealgorithms/scheduling/RandomSchedulingTest.java @@ -0,0 +1,93 @@ +package com.thealgorithms.scheduling; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Random; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class RandomSchedulingTest { + + private RandomScheduling randomScheduling; + private Random mockRandom; + + @BeforeEach + public void setup() { + mockRandom = mock(Random.class); // Mocking Random for predictable behavior + } + + @Test + public void testRandomOrder1() { + // Arrange + List tasks = List.of("Task1", "Task2", "Task3"); + // Mock the random sequence to control shuffling: swap 0 <-> 1, and 1 <-> 2. + when(mockRandom.nextInt(anyInt())).thenReturn(1, 2, 0); + randomScheduling = new RandomScheduling(tasks, mockRandom); + + // Act + List result = randomScheduling.schedule(); + + // Assert + assertEquals(List.of("Task1", "Task2", "Task3"), result); + } + + @Test + public void testRandomOrder2() { + // Arrange + List tasks = List.of("A", "B", "C", "D"); + // Mocking predictable swaps for the sequence: [C, B, D, A] + when(mockRandom.nextInt(anyInt())).thenReturn(2, 1, 3, 0); + randomScheduling = new RandomScheduling(tasks, mockRandom); + + // Act + List result = randomScheduling.schedule(); + + // Assert + assertEquals(List.of("A", "C", "B", "D"), result); + } + + @Test + public void testSingleTask() { + // Arrange + List tasks = List.of("SingleTask"); + when(mockRandom.nextInt(anyInt())).thenReturn(0); // No real shuffle + randomScheduling = new RandomScheduling(tasks, mockRandom); + + // Act + List result = randomScheduling.schedule(); + + // Assert + assertEquals(List.of("SingleTask"), result); + } + + @Test + public void testEmptyTaskList() { + // Arrange + List tasks = List.of(); + randomScheduling = new RandomScheduling(tasks, mockRandom); + + // Act + List result = randomScheduling.schedule(); + + // Assert + assertEquals(List.of(), result); // Should return an empty list + } + + @Test + public void testSameTasksMultipleTimes() { + // Arrange + List tasks = List.of("X", "X", "Y", "Z"); + when(mockRandom.nextInt(anyInt())).thenReturn(3, 0, 1, 2); + randomScheduling = new RandomScheduling(tasks, mockRandom); + + // Act + List result = randomScheduling.schedule(); + + // Assert + assertEquals(List.of("Y", "X", "X", "Z"), result); + } +} diff --git a/src/test/java/com/thealgorithms/scheduling/SelfAdjustingSchedulingTest.java b/src/test/java/com/thealgorithms/scheduling/SelfAdjustingSchedulingTest.java new file mode 100644 index 000000000000..8675a1ec397d --- /dev/null +++ b/src/test/java/com/thealgorithms/scheduling/SelfAdjustingSchedulingTest.java @@ -0,0 +1,57 @@ +package com.thealgorithms.scheduling; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SelfAdjustingSchedulingTest { + + private SelfAdjustingScheduling scheduler; + + @BeforeEach + public void setup() { + scheduler = new SelfAdjustingScheduling(); + } + + @Test + public void testAddAndScheduleSingleTask() { + scheduler.addTask("Task1", 5); + assertEquals("Task1", scheduler.scheduleNext()); + } + + @Test + public void testAddMultipleTasks() { + scheduler.addTask("Task1", 5); + scheduler.addTask("Task2", 1); + scheduler.addTask("Task3", 3); + assertEquals("Task2", scheduler.scheduleNext()); + assertEquals("Task2", scheduler.scheduleNext()); + assertEquals("Task3", scheduler.scheduleNext()); + } + + @Test + public void testPriorityAdjustment() { + scheduler.addTask("Task1", 1); + scheduler.addTask("Task2", 1); + scheduler.scheduleNext(); + scheduler.scheduleNext(); + scheduler.scheduleNext(); + assertEquals("Task2", scheduler.scheduleNext()); + } + + @Test + public void testEmptyScheduler() { + assertNull(scheduler.scheduleNext()); + } + + @Test + public void testTaskReschedulingAfterWait() { + scheduler.addTask("Task1", 1); + scheduler.addTask("Task2", 2); + scheduler.scheduleNext(); + scheduler.scheduleNext(); + assertEquals("Task1", scheduler.scheduleNext()); + } +} diff --git a/src/test/java/com/thealgorithms/scheduling/SlackTimeSchedulingTest.java b/src/test/java/com/thealgorithms/scheduling/SlackTimeSchedulingTest.java new file mode 100644 index 000000000000..555a0941e7f5 --- /dev/null +++ b/src/test/java/com/thealgorithms/scheduling/SlackTimeSchedulingTest.java @@ -0,0 +1,48 @@ +package com.thealgorithms.scheduling; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SlackTimeSchedulingTest { + + private SlackTimeScheduling scheduler; + + @BeforeEach + public void setup() { + scheduler = new SlackTimeScheduling(); + } + + @Test + public void testAddAndScheduleSingleTask() { + scheduler.addTask("Task1", 2, 5); + List expected = List.of("Task1"); + assertEquals(expected, scheduler.scheduleTasks()); + } + + @Test + public void testScheduleMultipleTasks() { + scheduler.addTask("Task1", 2, 5); + scheduler.addTask("Task2", 1, 4); + scheduler.addTask("Task3", 3, 7); + List expected = List.of("Task1", "Task2", "Task3"); + assertEquals(expected, scheduler.scheduleTasks()); + } + + @Test + public void testScheduleTasksWithSameSlackTime() { + scheduler.addTask("Task1", 2, 5); + scheduler.addTask("Task2", 3, 6); + scheduler.addTask("Task3", 1, 4); + List expected = List.of("Task1", "Task2", "Task3"); + assertEquals(expected, scheduler.scheduleTasks()); + } + + @Test + public void testEmptyScheduler() { + List expected = List.of(); + assertEquals(expected, scheduler.scheduleTasks()); + } +} diff --git a/src/test/java/com/thealgorithms/scheduling/diskscheduling/CircularLookSchedulingTest.java b/src/test/java/com/thealgorithms/scheduling/diskscheduling/CircularLookSchedulingTest.java new file mode 100644 index 000000000000..55429e41b84d --- /dev/null +++ b/src/test/java/com/thealgorithms/scheduling/diskscheduling/CircularLookSchedulingTest.java @@ -0,0 +1,55 @@ +package com.thealgorithms.scheduling.diskscheduling; + +import static java.util.Collections.emptyList; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class CircularLookSchedulingTest { + + @Test + public void testCircularLookSchedulingMovingUp() { + CircularLookScheduling scheduling = new CircularLookScheduling(50, true, 200); + List requests = Arrays.asList(55, 58, 39, 18, 90, 160, 150); + List expected = Arrays.asList(55, 58, 90, 150, 160, 18, 39); + + List result = scheduling.execute(requests); + assertEquals(expected, result); + } + + @Test + public void testCircularLookSchedulingMovingDown() { + CircularLookScheduling scheduling = new CircularLookScheduling(50, false, 200); + List requests = Arrays.asList(55, 58, 39, 18, 90, 160, 150); + List expected = Arrays.asList(39, 18, 160, 150, 90, 58, 55); + + List result = scheduling.execute(requests); + assertEquals(expected, result); + } + + @Test + public void testCircularLookSchedulingEmptyRequests() { + CircularLookScheduling scheduling = new CircularLookScheduling(50, true, 200); + List requests = emptyList(); + List expected = emptyList(); + + List result = scheduling.execute(requests); + assertEquals(expected, result); + } + + @Test + public void testCircularLookSchedulingPrintStatus() { + CircularLookScheduling scheduling = new CircularLookScheduling(50, true, 200); + List requests = Arrays.asList(55, 58, 39, 18, 90, 160, 150); + List result = scheduling.execute(requests); + + // Print the final status + System.out.println("Final CircularLookScheduling Position: " + scheduling.getCurrentPosition()); + System.out.println("CircularLookScheduling Moving Up: " + scheduling.isMovingUp()); + + // Print the order of request processing + System.out.println("Request Order: " + result); + } +} diff --git a/src/test/java/com/thealgorithms/scheduling/diskscheduling/CircularScanSchedulingTest.java b/src/test/java/com/thealgorithms/scheduling/diskscheduling/CircularScanSchedulingTest.java new file mode 100644 index 000000000000..38daf5104b82 --- /dev/null +++ b/src/test/java/com/thealgorithms/scheduling/diskscheduling/CircularScanSchedulingTest.java @@ -0,0 +1,49 @@ +package com.thealgorithms.scheduling.diskscheduling; + +import static java.util.Collections.emptyList; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class CircularScanSchedulingTest { + + @Test + public void testCircularScanSchedulingMovingUp() { + CircularScanScheduling circularScan = new CircularScanScheduling(50, true, 200); + List requests = Arrays.asList(55, 58, 39, 18, 90, 160, 150); + List expectedOrder = Arrays.asList(55, 58, 90, 150, 160, 18, 39); + + List result = circularScan.execute(requests); + assertEquals(expectedOrder, result); + + System.out.println("Final CircularScan Position: " + circularScan.getCurrentPosition()); + System.out.println("CircularScan Moving Up: " + circularScan.isMovingUp()); + System.out.println("Request Order: " + result); + } + + @Test + public void testCircularScanSchedulingMovingDown() { + CircularScanScheduling circularScan = new CircularScanScheduling(50, false, 200); + List requests = Arrays.asList(55, 58, 39, 18, 90, 160, 150); + List expectedOrder = Arrays.asList(39, 18, 160, 150, 90, 58, 55); + + List result = circularScan.execute(requests); + assertEquals(expectedOrder, result); + + System.out.println("Final CircularScan Position: " + circularScan.getCurrentPosition()); + System.out.println("CircularScan Moving Down: " + circularScan.isMovingUp()); + System.out.println("Request Order: " + result); + } + + @Test + public void testCircularScanSchedulingEmptyRequests() { + CircularScanScheduling circularScan = new CircularScanScheduling(50, true, 200); + List requests = emptyList(); + List expectedOrder = emptyList(); + + List result = circularScan.execute(requests); + assertEquals(expectedOrder, result); + } +} diff --git a/src/test/java/com/thealgorithms/scheduling/diskscheduling/LookSchedulingTest.java b/src/test/java/com/thealgorithms/scheduling/diskscheduling/LookSchedulingTest.java new file mode 100644 index 000000000000..43ef1a698b55 --- /dev/null +++ b/src/test/java/com/thealgorithms/scheduling/diskscheduling/LookSchedulingTest.java @@ -0,0 +1,68 @@ +package com.thealgorithms.scheduling.diskscheduling; + +import static java.util.Collections.emptyList; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class LookSchedulingTest { + + @Test + public void testLookSchedulingMovingUp() { + LookScheduling lookScheduling = new LookScheduling(50, true, 200); + List requests = Arrays.asList(55, 58, 39, 18, 90, 160, 150); + List expected = Arrays.asList(55, 58, 90, 150, 160, 39, 18); + + List result = lookScheduling.execute(requests); + assertEquals(expected, result); + } + + @Test + public void testLookSchedulingMovingDown() { + LookScheduling lookScheduling = new LookScheduling(50, false, 200); + List requests = Arrays.asList(55, 58, 39, 18, 90, 160, 150); + List expected = Arrays.asList(39, 18, 55, 58, 90, 150, 160); + + List result = lookScheduling.execute(requests); + assertEquals(expected, result); + } + + @Test + public void testLookSchedulingEmptyRequests() { + LookScheduling lookScheduling = new LookScheduling(50, true, 200); + List requests = emptyList(); + List expected = emptyList(); + + List result = lookScheduling.execute(requests); + assertEquals(expected, result); + } + + @Test + public void testLookSchedulingCurrentPosition() { + LookScheduling lookScheduling = new LookScheduling(50, true, 200); + + // Testing current position remains unchanged after scheduling. + assertEquals(50, lookScheduling.getCurrentPosition()); + } + + @Test + public void testLookSchedulingPrintStatus() { + LookScheduling lookScheduling = new LookScheduling(50, true, 200); + + List requests = Arrays.asList(55, 58, 39, 18, 90, 160, 150); + + List result = lookScheduling.execute(requests); + + List expectedOrder = Arrays.asList(55, 58, 90, 150, 160, 39, 18); + assertEquals(expectedOrder, result); + + System.out.println("Final LookScheduling Position: " + lookScheduling.getCurrentPosition()); + System.out.println("LookScheduling Moving Up: " + lookScheduling.isMovingUp()); + + System.out.println("Farthest Position Reached: " + lookScheduling.getFarthestPosition()); + + System.out.println("Request Order: " + result); + } +} diff --git a/src/test/java/com/thealgorithms/scheduling/diskscheduling/SSFSchedulingTest.java b/src/test/java/com/thealgorithms/scheduling/diskscheduling/SSFSchedulingTest.java new file mode 100644 index 000000000000..0239b0117f1b --- /dev/null +++ b/src/test/java/com/thealgorithms/scheduling/diskscheduling/SSFSchedulingTest.java @@ -0,0 +1,55 @@ +package com.thealgorithms.scheduling.diskscheduling; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SSFSchedulingTest { + + private SSFScheduling scheduler; + + @BeforeEach + public void setUp() { + scheduler = new SSFScheduling(50); + } + + @Test + public void testExecuteWithEmptyList() { + List requests = new ArrayList<>(); + List result = scheduler.execute(requests); + assertTrue(result.isEmpty(), "Result should be empty for an empty request list."); + } + + @Test + public void testExecuteWithSingleRequest() { + List requests = new ArrayList<>(List.of(100)); + List result = scheduler.execute(requests); + assertEquals(List.of(100), result, "The only request should be served first."); + } + + @Test + public void testExecuteWithMultipleRequests() { + List requests = new ArrayList<>(List.of(10, 90, 60, 40, 30, 70)); + List result = scheduler.execute(requests); + assertEquals(List.of(60, 70, 90, 40, 30, 10), result, "Requests should be served in the shortest seek first order."); + } + + @Test + public void testExecuteWithSameDistanceRequests() { + List requests = new ArrayList<>(List.of(45, 55)); + List result = scheduler.execute(requests); + assertEquals(List.of(45, 55), result, "When distances are equal, requests should be served in the order they appear in the list."); + } + + @Test + public void testGetCurrentPositionAfterExecution() { + List requests = new ArrayList<>(List.of(10, 90, 60, 40, 30, 70)); + scheduler.execute(requests); + int currentPosition = scheduler.getCurrentPosition(); + assertEquals(10, currentPosition, "Current position should be the last request after execution."); + } +} diff --git a/src/test/java/com/thealgorithms/scheduling/diskscheduling/ScanSchedulingTest.java b/src/test/java/com/thealgorithms/scheduling/diskscheduling/ScanSchedulingTest.java new file mode 100644 index 000000000000..d1525d9a9c0d --- /dev/null +++ b/src/test/java/com/thealgorithms/scheduling/diskscheduling/ScanSchedulingTest.java @@ -0,0 +1,55 @@ +package com.thealgorithms.scheduling.diskscheduling; + +import static java.util.Collections.emptyList; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class ScanSchedulingTest { + + @Test + public void testScanSchedulingMovingUp() { + ScanScheduling scanScheduling = new ScanScheduling(50, true, 200); + List requests = Arrays.asList(55, 58, 39, 18, 90, 160, 150); + List expected = Arrays.asList(55, 58, 90, 150, 160, 199, 39, 18); + + List result = scanScheduling.execute(requests); + assertEquals(expected, result); + } + + @Test + public void testScanSchedulingMovingDown() { + ScanScheduling scanScheduling = new ScanScheduling(50, false, 200); + List requests = Arrays.asList(55, 58, 39, 18, 90, 160, 150); + List expected = Arrays.asList(39, 18, 0, 55, 58, 90, 150, 160); + + List result = scanScheduling.execute(requests); + assertEquals(expected, result); + } + + @Test + public void testScanSchedulingEmptyRequests() { + ScanScheduling scanScheduling = new ScanScheduling(50, true, 200); + List requests = emptyList(); + List expected = emptyList(); + + List result = scanScheduling.execute(requests); + assertEquals(expected, result); + } + + @Test + public void testScanScheduling() { + ScanScheduling scanScheduling = new ScanScheduling(50, true, 200); + List requests = Arrays.asList(55, 58, 39, 18, 90, 160, 150); + + List result = scanScheduling.execute(requests); + List expectedOrder = Arrays.asList(55, 58, 90, 150, 160, 199, 39, 18); + assertEquals(expectedOrder, result); + + System.out.println("Final Head Position: " + scanScheduling.getHeadPosition()); + System.out.println("Head Moving Up: " + scanScheduling.isMovingUp()); + System.out.println("Request Order: " + result); + } +} diff --git a/src/test/java/com/thealgorithms/searches/BM25InvertedIndexTest.java b/src/test/java/com/thealgorithms/searches/BM25InvertedIndexTest.java new file mode 100644 index 000000000000..2017c11dfb3c --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/BM25InvertedIndexTest.java @@ -0,0 +1,95 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * Test Cases for Inverted Index with BM25 + * @author Prayas Kumar (https://github.com/prayas7102) + */ + +class BM25InvertedIndexTest { + + private static BM25InvertedIndex index; + + @BeforeAll + static void setUp() { + index = new BM25InvertedIndex(); + index.addMovie(1, "The Shawshank Redemption", 9.3, 1994, "Hope is a good thing. Maybe the best of things. And no good thing ever dies."); + index.addMovie(2, "The Godfather", 9.2, 1972, "I'm gonna make him an offer he can't refuse."); + index.addMovie(3, "The Dark Knight", 9.0, 2008, "You either die a hero or live long enough to see yourself become the villain."); + index.addMovie(4, "Pulp Fiction", 8.9, 1994, "You know what they call a Quarter Pounder with Cheese in Paris? They call it a Royale with Cheese."); + index.addMovie(5, "Good Will Hunting", 8.3, 1997, "Will Hunting is a genius and he has a good heart. The best of his abilities is yet to be explored."); + index.addMovie(6, "It's a Wonderful Life", 8.6, 1946, "Each man's life touches so many other lives. If he wasn't around, it would leave an awfully good hole."); + index.addMovie(7, "The Pursuit of Happyness", 8.0, 2006, "It was the pursuit of a better life, and a good opportunity to change things for the better."); + index.addMovie(8, "A Few Good Men", 7.7, 1992, "You can't handle the truth! This movie has a lot of good moments and intense drama."); + } + + @Test + void testAddMovie() { + // Check that the index contains the correct number of movies + int moviesLength = index.getMoviesLength(); + assertEquals(8, moviesLength); + } + + @Test + void testSearchForTermFound() { + int expected = 1; + List result = index.search("hope"); + int actual = result.getFirst().getDocId(); + assertEquals(expected, actual); + } + + @Test + void testSearchRanking() { + // Perform search for the term "good" + List results = index.search("good"); + assertFalse(results.isEmpty()); + for (SearchResult result : results) { + System.out.println(result); + } + // Validate the ranking based on the provided relevance scores + assertEquals(1, results.get(0).getDocId()); // The Shawshank Redemption should be ranked 1st + assertEquals(8, results.get(1).getDocId()); // A Few Good Men should be ranked 2nd + assertEquals(5, results.get(2).getDocId()); // Good Will Hunting should be ranked 3rd + assertEquals(7, results.get(3).getDocId()); // The Pursuit of Happyness should be ranked 4th + assertEquals(6, results.get(4).getDocId()); // It's a Wonderful Life should be ranked 5th + + // Ensure the relevance scores are in descending order + for (int i = 0; i < results.size() - 1; i++) { + assertTrue(results.get(i).getRelevanceScore() > results.get(i + 1).getRelevanceScore()); + } + } + + @Test + void testSearchForTermNotFound() { + List results = index.search("nonexistent"); + assertTrue(results.isEmpty()); + } + + @Test + void testSearchForCommonTerm() { + List results = index.search("the"); + assertFalse(results.isEmpty()); + assertTrue(results.size() > 1); + } + + @Test + void testBM25ScoreCalculation() { + List results = index.search("cheese"); + assertEquals(1, results.size()); + assertEquals(4, results.getFirst().docId); // Pulp Fiction should have the highest score + } + + @Test + void testCaseInsensitivity() { + List resultsLowerCase = index.search("hope"); + List resultsUpperCase = index.search("HOPE"); + assertEquals(resultsLowerCase, resultsUpperCase); + } +} diff --git a/src/test/java/com/thealgorithms/searches/BinarySearchTest.java b/src/test/java/com/thealgorithms/searches/BinarySearchTest.java new file mode 100644 index 000000000000..bd4620a7fa7d --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/BinarySearchTest.java @@ -0,0 +1,108 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.IntStream; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the BinarySearch class. + */ +class BinarySearchTest { + + /** + * Test for basic binary search functionality. + */ + @Test + void testBinarySearchFound() { + BinarySearch binarySearch = new BinarySearch(); + Integer[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + int key = 7; + int expectedIndex = 6; // Index of the key in the array + assertEquals(expectedIndex, binarySearch.find(array, key), "The index of the found element should be 6."); + } + + /** + * Test for binary search when the element is not present. + */ + @Test + void testBinarySearchNotFound() { + BinarySearch binarySearch = new BinarySearch(); + Integer[] array = {1, 2, 3, 4, 5}; + int key = 6; // Element not present in the array + 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 first element as the key. + */ + @Test + void testBinarySearchFirstElement() { + BinarySearch binarySearch = new BinarySearch(); + Integer[] array = {1, 2, 3, 4, 5}; + int key = 1; // First element + int expectedIndex = 0; // Index of the key in the array + assertEquals(expectedIndex, binarySearch.find(array, key), "The index of the first element should be 0."); + } + + /** + * Test for binary search with last element as the key. + */ + @Test + void testBinarySearchLastElement() { + BinarySearch binarySearch = new BinarySearch(); + Integer[] array = {1, 2, 3, 4, 5}; + int key = 5; // Last element + int expectedIndex = 4; // Index of the key in the array + assertEquals(expectedIndex, binarySearch.find(array, key), "The index of the last element should be 4."); + } + + /** + * Test for binary search with a single element present. + */ + @Test + void testBinarySearchSingleElementFound() { + BinarySearch binarySearch = new BinarySearch(); + Integer[] array = {1}; + int key = 1; // Only element present + int expectedIndex = 0; // Index of the key in the array + assertEquals(expectedIndex, binarySearch.find(array, key), "The index of the single element should be 0."); + } + + /** + * Test for binary search with a single element not present. + */ + @Test + void testBinarySearchSingleElementNotFound() { + BinarySearch binarySearch = new BinarySearch(); + Integer[] array = {1}; + int key = 2; // Key not present + 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 an empty array. + */ + @Test + void testBinarySearchEmptyArray() { + BinarySearch binarySearch = new BinarySearch(); + Integer[] array = {}; // Empty array + int key = 1; // Key not present + int expectedIndex = -1; // Key not found + assertEquals(expectedIndex, binarySearch.find(array, key), "The element should not be found in an empty array."); + } + + /** + * Test for binary search on large array. + */ + @Test + void testBinarySearchLargeArray() { + BinarySearch binarySearch = new BinarySearch(); + Integer[] array = IntStream.range(0, 10000).boxed().toArray(Integer[] ::new); // Array from 0 to 9999 + int key = 9999; // Last element + int expectedIndex = 9999; // Index of the last element + assertEquals(expectedIndex, binarySearch.find(array, key), "The index of the last element should be 9999."); + } +} diff --git a/src/test/java/com/thealgorithms/searches/ExponentialSearchTest.java b/src/test/java/com/thealgorithms/searches/ExponentialSearchTest.java new file mode 100644 index 000000000000..c84da531e8a4 --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/ExponentialSearchTest.java @@ -0,0 +1,84 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.IntStream; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the ExponentialSearch class. + */ +class ExponentialSearchTest { + + /** + * Test for basic exponential search functionality. + */ + @Test + void testExponentialSearchFound() { + ExponentialSearch exponentialSearch = new ExponentialSearch(); + Integer[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + int key = 7; + int expectedIndex = 6; // Index of the key in the array + assertEquals(expectedIndex, exponentialSearch.find(array, key), "The index of the found element should be 6."); + } + + /** + * Test for exponential search with the first element as the key. + */ + @Test + void testExponentialSearchFirstElement() { + ExponentialSearch exponentialSearch = new ExponentialSearch(); + Integer[] array = {1, 2, 3, 4, 5}; + int key = 1; // First element + int expectedIndex = 0; // Index of the key in the array + assertEquals(expectedIndex, exponentialSearch.find(array, key), "The index of the first element should be 0."); + } + + /** + * Test for exponential search with the last element as the key. + */ + @Test + void testExponentialSearchLastElement() { + ExponentialSearch exponentialSearch = new ExponentialSearch(); + Integer[] array = {1, 2, 3, 4, 5}; + int key = 5; // Last element + int expectedIndex = 4; // Index of the key in the array + assertEquals(expectedIndex, exponentialSearch.find(array, key), "The index of the last element should be 4."); + } + + /** + * Test for exponential search with a single element present. + */ + @Test + void testExponentialSearchSingleElementFound() { + ExponentialSearch exponentialSearch = new ExponentialSearch(); + Integer[] array = {1}; + int key = 1; // Only element present + int expectedIndex = 0; // Index of the key in the array + assertEquals(expectedIndex, exponentialSearch.find(array, key), "The index of the single element should be 0."); + } + + /** + * Test for exponential search with an empty array. + */ + @Test + void testExponentialSearchEmptyArray() { + ExponentialSearch exponentialSearch = new ExponentialSearch(); + Integer[] array = {}; // Empty array + int key = 1; // Key not present + int expectedIndex = -1; // Key not found + assertEquals(expectedIndex, exponentialSearch.find(array, key), "The element should not be found in an empty array."); + } + + /** + * Test for exponential search on large array. + */ + @Test + void testExponentialSearchLargeArray() { + ExponentialSearch exponentialSearch = new ExponentialSearch(); + Integer[] array = IntStream.range(0, 10000).boxed().toArray(Integer[] ::new); // Array from 0 to 9999 + int key = 9999; + int expectedIndex = 9999; + assertEquals(expectedIndex, exponentialSearch.find(array, key), "The index of the last element should be 9999."); + } +} diff --git a/src/test/java/com/thealgorithms/searches/FibonacciSearchTest.java b/src/test/java/com/thealgorithms/searches/FibonacciSearchTest.java new file mode 100644 index 000000000000..801c33b1d09a --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/FibonacciSearchTest.java @@ -0,0 +1,124 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.stream.IntStream; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the FibonacciSearch class. + */ +class FibonacciSearchTest { + + /** + * Test for basic Fibonacci search functionality. + */ + @Test + void testFibonacciSearchFound() { + FibonacciSearch fibonacciSearch = new FibonacciSearch(); + Integer[] array = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512}; + int key = 128; + int expectedIndex = 7; // Index of the key in the array + assertEquals(expectedIndex, fibonacciSearch.find(array, key), "The index of the found element should be 7."); + } + + /** + * Test for Fibonacci search when the element is not present. + */ + @Test + void testFibonacciSearchNotFound() { + FibonacciSearch fibonacciSearch = new FibonacciSearch(); + Integer[] array = {1, 2, 4, 8, 16}; + int key = 6; // Element not present in the array + int expectedIndex = -1; // Key not found + assertEquals(expectedIndex, fibonacciSearch.find(array, key), "The element should not be found in the array."); + } + + /** + * Test for Fibonacci search with the first element as the key. + */ + @Test + void testFibonacciSearchFirstElement() { + FibonacciSearch fibonacciSearch = new FibonacciSearch(); + Integer[] array = {1, 2, 4, 8, 16}; + int key = 1; // First element + int expectedIndex = 0; // Index of the key in the array + assertEquals(expectedIndex, fibonacciSearch.find(array, key), "The index of the first element should be 0."); + } + + /** + * Test for Fibonacci search with the last element as the key. + */ + @Test + void testFibonacciSearchLastElement() { + FibonacciSearch fibonacciSearch = new FibonacciSearch(); + Integer[] array = {1, 2, 4, 8, 16}; + int key = 16; // Last element + int expectedIndex = 4; // Index of the key in the array + assertEquals(expectedIndex, fibonacciSearch.find(array, key), "The index of the last element should be 4."); + } + + /** + * Test for Fibonacci search with a single element present. + */ + @Test + void testFibonacciSearchSingleElementFound() { + FibonacciSearch fibonacciSearch = new FibonacciSearch(); + Integer[] array = {1}; + int key = 1; // Only element present + int expectedIndex = 0; // Index of the key in the array + assertEquals(expectedIndex, fibonacciSearch.find(array, key), "The index of the single element should be 0."); + } + + /** + * Test for Fibonacci search with a single element not present. + */ + @Test + void testFibonacciSearchSingleElementNotFound() { + FibonacciSearch fibonacciSearch = new FibonacciSearch(); + Integer[] array = {1}; + int key = 2; // Key not present + int expectedIndex = -1; // Key not found + assertEquals(expectedIndex, fibonacciSearch.find(array, key), "The element should not be found in the array."); + } + + /** + * Test for Fibonacci search with an empty array. + */ + @Test + void testFibonacciSearchEmptyArray() { + FibonacciSearch fibonacciSearch = new FibonacciSearch(); + Integer[] array = {}; // Empty array + int key = 1; // Key not present + assertThrows(IllegalArgumentException.class, () -> fibonacciSearch.find(array, key), "An empty array should throw an IllegalArgumentException."); + } + + @Test + void testFibonacciSearchUnsortedArray() { + FibonacciSearch fibonacciSearch = new FibonacciSearch(); + Integer[] array = {2, 1, 4, 3, 6, 5}; + int key = 3; // Key not present + assertThrows(IllegalArgumentException.class, () -> fibonacciSearch.find(array, key), "An unsorted array should throw an IllegalArgumentException."); + } + + @Test + void testFibonacciSearchNullKey() { + FibonacciSearch fibonacciSearch = new FibonacciSearch(); + Integer[] array = {1, 2, 4, 8, 16}; + Integer key = null; // Null key + assertThrows(IllegalArgumentException.class, () -> fibonacciSearch.find(array, key), "A null key should throw an IllegalArgumentException."); + } + + /** + * Test for Fibonacci search on large array. + */ + @Test + void testFibonacciSearchLargeArray() { + FibonacciSearch fibonacciSearch = new FibonacciSearch(); + Integer[] array = IntStream.range(0, 10000).boxed().toArray(Integer[] ::new); // Array from 0 to 9999 + int key = 9999; + int expectedIndex = 9999; + assertEquals(expectedIndex, fibonacciSearch.find(array, key), "The index of the last element should be 9999."); + } +} diff --git a/src/test/java/com/thealgorithms/searches/InterpolationSearchTest.java b/src/test/java/com/thealgorithms/searches/InterpolationSearchTest.java new file mode 100644 index 000000000000..b3b7e7ef129c --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/InterpolationSearchTest.java @@ -0,0 +1,90 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.IntStream; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the InterpolationSearch class. + */ +class InterpolationSearchTest { + + /** + * Test for basic interpolation search functionality when the element is found. + */ + @Test + void testInterpolationSearchFound() { + InterpolationSearch interpolationSearch = new InterpolationSearch(); + int[] array = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512}; + int key = 128; + int expectedIndex = 7; // Index of the key in the array + assertEquals(expectedIndex, interpolationSearch.find(array, key), "The index of the found element should be 7."); + } + + /** + * Test for interpolation search when the element is not present in the array. + */ + @Test + void testInterpolationSearchNotFound() { + InterpolationSearch interpolationSearch = new InterpolationSearch(); + int[] array = {1, 2, 4, 8, 16}; + int key = 6; // Element not present in the array + assertEquals(-1, interpolationSearch.find(array, key), "The element should not be found in the array."); + } + + /** + * Test for interpolation search with the first element as the key. + */ + @Test + void testInterpolationSearchFirstElement() { + InterpolationSearch interpolationSearch = new InterpolationSearch(); + int[] array = {1, 2, 4, 8, 16}; + int key = 1; // First element + assertEquals(0, interpolationSearch.find(array, key), "The index of the first element should be 0."); + } + + /** + * Test for interpolation search with a single element not present. + */ + @Test + void testInterpolationSearchSingleElementNotFound() { + InterpolationSearch interpolationSearch = new InterpolationSearch(); + int[] array = {1}; + int key = 2; // Key not present + assertEquals(-1, interpolationSearch.find(array, key), "The element should not be found in the array."); + } + + /** + * Test for interpolation search with an empty array. + */ + @Test + void testInterpolationSearchEmptyArray() { + InterpolationSearch interpolationSearch = new InterpolationSearch(); + int[] array = {}; // Empty array + int key = 1; // Key not present + assertEquals(-1, interpolationSearch.find(array, key), "The element should not be found in an empty array."); + } + + /** + * Test for interpolation search on large uniformly distributed array. + */ + @Test + void testInterpolationSearchLargeUniformArray() { + InterpolationSearch interpolationSearch = new InterpolationSearch(); + int[] array = IntStream.range(0, 10000).map(i -> i * 2).toArray(); // Array from 0 to 19998, step 2 + int key = 9998; // Last even number in the array + assertEquals(4999, interpolationSearch.find(array, key), "The index of the last element should be 4999."); + } + + /** + * Test for interpolation search on large non-uniformly distributed array. + */ + @Test + void testInterpolationSearchLargeNonUniformArray() { + InterpolationSearch interpolationSearch = new InterpolationSearch(); + int[] array = {1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144}; // Fibonacci numbers + int key = 21; // Present in the array + assertEquals(6, interpolationSearch.find(array, key), "The index of the found element should be 6."); + } +} diff --git a/src/test/java/com/thealgorithms/searches/IterativeBinarySearchTest.java b/src/test/java/com/thealgorithms/searches/IterativeBinarySearchTest.java new file mode 100644 index 000000000000..b2e121ac1ba0 --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/IterativeBinarySearchTest.java @@ -0,0 +1,117 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the IterativeBinarySearch class. + */ +class IterativeBinarySearchTest { + + /** + * Test for basic binary search functionality when the element is found. + */ + @Test + void testBinarySearchFound() { + IterativeBinarySearch binarySearch = new IterativeBinarySearch(); + Integer[] array = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512}; + Integer key = 128; + int expectedIndex = 7; // Index of the key in the array + assertEquals(expectedIndex, binarySearch.find(array, key), "The index of the found element should be 7."); + } + + /** + * Test for binary search when the element is not present in the array. + */ + @Test + void testBinarySearchNotFound() { + IterativeBinarySearch binarySearch = new IterativeBinarySearch(); + Integer[] array = {1, 2, 4, 8, 16}; + Integer key = 6; // Element not present in the array + assertEquals(-1, binarySearch.find(array, key), "The element should not be found in the array."); + } + + /** + * Test for binary search with the first element as the key. + */ + @Test + void testBinarySearchFirstElement() { + IterativeBinarySearch binarySearch = new IterativeBinarySearch(); + Integer[] array = {1, 2, 4, 8, 16}; + Integer key = 1; // First element + assertEquals(0, binarySearch.find(array, key), "The index of the first element should be 0."); + } + + /** + * Test for binary search with the last element as the key. + */ + @Test + void testBinarySearchLastElement() { + IterativeBinarySearch binarySearch = new IterativeBinarySearch(); + Integer[] array = {1, 2, 4, 8, 16}; + Integer key = 16; // Last element + assertEquals(4, binarySearch.find(array, key), "The index of the last element should be 4."); + } + + /** + * Test for binary search with a single element present. + */ + @Test + void testBinarySearchSingleElementFound() { + IterativeBinarySearch binarySearch = new IterativeBinarySearch(); + Integer[] array = {1}; + Integer key = 1; // Only element present + assertEquals(0, binarySearch.find(array, key), "The index of the single element should be 0."); + } + + /** + * Test for binary search with a single element not present. + */ + @Test + void testBinarySearchSingleElementNotFound() { + IterativeBinarySearch binarySearch = new IterativeBinarySearch(); + Integer[] array = {1}; + Integer key = 2; // Key not present + assertEquals(-1, binarySearch.find(array, key), "The element should not be found in the array."); + } + + /** + * Test for binary search with an empty array. + */ + @Test + void testBinarySearchEmptyArray() { + IterativeBinarySearch binarySearch = new IterativeBinarySearch(); + Integer[] array = {}; // Empty array + Integer key = 1; // Key not present + assertEquals(-1, binarySearch.find(array, key), "The element should not be found in an empty array."); + } + + /** + * Test for binary search on a large array. + */ + @Test + void testBinarySearchLargeArray() { + IterativeBinarySearch binarySearch = new IterativeBinarySearch(); + Integer[] array = new Integer[10000]; + for (int i = 0; i < array.length; i++) { + array[i] = i * 2; + } // Array from 0 to 19998, step 2 + Integer key = 9998; // Present in the array + assertEquals(4999, binarySearch.find(array, key), "The index of the found element should be 4999."); + } + + /** + * Test for binary search on large array with a non-existent key. + */ + @Test + void testBinarySearchLargeArrayNotFound() { + IterativeBinarySearch binarySearch = new IterativeBinarySearch(); + Integer[] array = new Integer[10000]; + for (int i = 0; i < array.length; i++) { + array[i] = i * 2; + } // Array from 0 to 19998, step 2 + Integer key = 9999; // Key not present + assertEquals(-1, binarySearch.find(array, key), "The element should not be found in the array."); + } +} diff --git a/src/test/java/com/thealgorithms/searches/IterativeTernarySearchTest.java b/src/test/java/com/thealgorithms/searches/IterativeTernarySearchTest.java new file mode 100644 index 000000000000..c7640e6d0672 --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/IterativeTernarySearchTest.java @@ -0,0 +1,117 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the IterativeTernarySearch class. + */ +class IterativeTernarySearchTest { + + /** + * Test for basic ternary search functionality when the element is found. + */ + @Test + void testTernarySearchFound() { + IterativeTernarySearch ternarySearch = new IterativeTernarySearch(); + Integer[] array = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512}; + Integer key = 128; + int expectedIndex = 7; // Index of the key in the array + assertEquals(expectedIndex, ternarySearch.find(array, key), "The index of the found element should be 7."); + } + + /** + * Test for ternary search when the element is not present in the array. + */ + @Test + void testTernarySearchNotFound() { + IterativeTernarySearch ternarySearch = new IterativeTernarySearch(); + Integer[] array = {1, 2, 4, 8, 16}; + Integer key = 6; // Element not present in the array + assertEquals(-1, ternarySearch.find(array, key), "The element should not be found in the array."); + } + + /** + * Test for ternary search with the first element as the key. + */ + @Test + void testTernarySearchFirstElement() { + IterativeTernarySearch ternarySearch = new IterativeTernarySearch(); + Integer[] array = {1, 2, 4, 8, 16}; + Integer key = 1; // First element + assertEquals(0, ternarySearch.find(array, key), "The index of the first element should be 0."); + } + + /** + * Test for ternary search with the last element as the key. + */ + @Test + void testTernarySearchLastElement() { + IterativeTernarySearch ternarySearch = new IterativeTernarySearch(); + Integer[] array = {1, 2, 4, 8, 16}; + Integer key = 16; // Last element + assertEquals(4, ternarySearch.find(array, key), "The index of the last element should be 4."); + } + + /** + * Test for ternary search with a single element present. + */ + @Test + void testTernarySearchSingleElementFound() { + IterativeTernarySearch ternarySearch = new IterativeTernarySearch(); + Integer[] array = {1}; + Integer key = 1; // Only element present + assertEquals(0, ternarySearch.find(array, key), "The index of the single element should be 0."); + } + + /** + * Test for ternary search with a single element not present. + */ + @Test + void testTernarySearchSingleElementNotFound() { + IterativeTernarySearch ternarySearch = new IterativeTernarySearch(); + Integer[] array = {1}; + Integer key = 2; // Key not present + assertEquals(-1, ternarySearch.find(array, key), "The element should not be found in the array."); + } + + /** + * Test for ternary search with an empty array. + */ + @Test + void testTernarySearchEmptyArray() { + IterativeTernarySearch ternarySearch = new IterativeTernarySearch(); + Integer[] array = {}; // Empty array + Integer key = 1; // Key not present + assertEquals(-1, ternarySearch.find(array, key), "The element should not be found in an empty array."); + } + + /** + * Test for ternary search on a large array. + */ + @Test + void testTernarySearchLargeArray() { + IterativeTernarySearch ternarySearch = new IterativeTernarySearch(); + Integer[] array = new Integer[10000]; + for (int i = 0; i < array.length; i++) { + array[i] = i * 2; + } // Array from 0 to 19998, step 2 + Integer key = 9998; // Present in the array + assertEquals(4999, ternarySearch.find(array, key), "The index of the found element should be 4999."); + } + + /** + * Test for ternary search on large array with a non-existent key. + */ + @Test + void testTernarySearchLargeArrayNotFound() { + IterativeTernarySearch ternarySearch = new IterativeTernarySearch(); + Integer[] array = new Integer[10000]; + for (int i = 0; i < array.length; i++) { + array[i] = i * 2; + } // Array from 0 to 19998, step 2 + Integer key = 9999; // Key not present + assertEquals(-1, ternarySearch.find(array, key), "The element should not be found in the array."); + } +} diff --git a/src/test/java/com/thealgorithms/searches/JumpSearchTest.java b/src/test/java/com/thealgorithms/searches/JumpSearchTest.java new file mode 100644 index 000000000000..3fa319b66a41 --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/JumpSearchTest.java @@ -0,0 +1,94 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the JumpSearch class. + */ +class JumpSearchTest { + + /** + * Test for finding an element present in the array. + */ + @Test + void testJumpSearchFound() { + JumpSearch jumpSearch = new JumpSearch(); + Integer[] array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Integer key = 5; // Element to find + assertEquals(5, jumpSearch.find(array, key), "The index of the found element should be 5."); + } + + /** + * Test for finding the first element in the array. + */ + @Test + void testJumpSearchFirstElement() { + JumpSearch jumpSearch = new JumpSearch(); + Integer[] array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Integer key = 0; // First element + assertEquals(0, jumpSearch.find(array, key), "The index of the first element should be 0."); + } + + /** + * Test for finding the last element in the array. + */ + @Test + void testJumpSearchLastElement() { + JumpSearch jumpSearch = new JumpSearch(); + Integer[] array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Integer key = 10; // Last element + assertEquals(10, jumpSearch.find(array, key), "The index of the last element should be 10."); + } + + /** + * Test for finding an element not present in the array. + */ + @Test + void testJumpSearchNotFound() { + JumpSearch jumpSearch = new JumpSearch(); + Integer[] array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Integer key = -1; // Element not in the array + assertEquals(-1, jumpSearch.find(array, key), "The element should not be found in the array."); + } + + /** + * Test for finding an element in an empty array. + */ + @Test + void testJumpSearchEmptyArray() { + JumpSearch jumpSearch = new JumpSearch(); + Integer[] array = {}; // Empty array + Integer key = 1; // Key not present + assertEquals(-1, jumpSearch.find(array, key), "The element should not be found in an empty array."); + } + + /** + * Test for finding an element in a large array. + */ + @Test + void testJumpSearchLargeArray() { + JumpSearch jumpSearch = new JumpSearch(); + Integer[] array = new Integer[1000]; + for (int i = 0; i < array.length; i++) { + array[i] = i * 2; // Fill the array with even numbers + } + Integer key = 256; // Present in the array + assertEquals(128, jumpSearch.find(array, key), "The index of the found element should be 128."); + } + + /** + * Test for finding an element in a large array when it is not present. + */ + @Test + void testJumpSearchLargeArrayNotFound() { + JumpSearch jumpSearch = new JumpSearch(); + Integer[] array = new Integer[1000]; + for (int i = 0; i < array.length; i++) { + array[i] = i * 2; // Fill the array with even numbers + } + Integer key = 999; // Key not present + assertEquals(-1, jumpSearch.find(array, key), "The element should not be found in the array."); + } +} diff --git a/src/test/java/com/thealgorithms/searches/LinearSearchTest.java b/src/test/java/com/thealgorithms/searches/LinearSearchTest.java new file mode 100644 index 000000000000..5c09dec6d578 --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/LinearSearchTest.java @@ -0,0 +1,118 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Random; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the LinearSearch class. + */ +class LinearSearchTest { + + /** + * Test for finding an element present in the array. + */ + @Test + void testLinearSearchFound() { + LinearSearch linearSearch = new LinearSearch(); + Integer[] array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Integer key = 5; // Element to find + assertEquals(5, linearSearch.find(array, key), "The index of the found element should be 5."); + } + + /** + * Test for finding the first element in the array. + */ + @Test + void testLinearSearchFirstElement() { + LinearSearch linearSearch = new LinearSearch(); + Integer[] array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Integer key = 0; // First element + assertEquals(0, linearSearch.find(array, key), "The index of the first element should be 0."); + } + + /** + * Test for finding the last element in the array. + */ + @Test + void testLinearSearchLastElement() { + LinearSearch linearSearch = new LinearSearch(); + Integer[] array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Integer key = 10; // Last element + assertEquals(10, linearSearch.find(array, key), "The index of the last element should be 10."); + } + + /** + * Test for finding an element not present in the array. + */ + @Test + void testLinearSearchNotFound() { + LinearSearch linearSearch = new LinearSearch(); + Integer[] array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Integer key = -1; // Element not in the array + assertEquals(-1, linearSearch.find(array, key), "The element should not be found in the array."); + } + + /** + * Test for finding an element in an empty array. + */ + @Test + void testLinearSearchEmptyArray() { + LinearSearch linearSearch = new LinearSearch(); + Integer[] array = {}; // Empty array + Integer key = 1; // Key not present + assertEquals(-1, linearSearch.find(array, key), "The element should not be found in an empty array."); + } + + /** + * Test for finding an element in a large array. + */ + @Test + void testLinearSearchLargeArray() { + LinearSearch linearSearch = new LinearSearch(); + Integer[] array = new Integer[1000]; + for (int i = 0; i < array.length; i++) { + array[i] = i; // Fill the array with integers 0 to 999 + } + Integer key = 256; // Present in the array + assertEquals(256, linearSearch.find(array, key), "The index of the found element should be 256."); + } + + /** + * Test for finding an element in a large array when it is not present. + */ + @Test + void testLinearSearchLargeArrayNotFound() { + LinearSearch linearSearch = new LinearSearch(); + Integer[] array = new Integer[1000]; + for (int i = 0; i < array.length; i++) { + array[i] = i; // Fill the array with integers 0 to 999 + } + Integer key = 1001; // Key not present + assertEquals(-1, linearSearch.find(array, key), "The element should not be found in the array."); + } + + /** + * Test for finding multiple occurrences of the same element in the array. + */ + @Test + void testLinearSearchMultipleOccurrences() { + LinearSearch linearSearch = new LinearSearch(); + Integer[] array = {1, 2, 3, 4, 5, 3, 6, 7, 3}; // 3 occurs multiple times + Integer key = 3; // Key to find + assertEquals(2, linearSearch.find(array, key), "The index of the first occurrence of the element should be 2."); + } + + /** + * Test for performance with random large array. + */ + @Test + void testLinearSearchRandomArray() { + LinearSearch linearSearch = new LinearSearch(); + Random random = new Random(); + Integer[] array = random.ints(0, 1000).distinct().limit(1000).boxed().toArray(Integer[] ::new); + Integer key = array[random.nextInt(array.length)]; // Key should be in the array + assertEquals(java.util.Arrays.asList(array).indexOf(key), linearSearch.find(array, key), "The index of the found element should match."); + } +} diff --git a/src/test/java/com/thealgorithms/searches/LinearSearchThreadTest.java b/src/test/java/com/thealgorithms/searches/LinearSearchThreadTest.java new file mode 100644 index 000000000000..534c2a4487b2 --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/LinearSearchThreadTest.java @@ -0,0 +1,74 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class LinearSearchThreadTest { + + /** + * Test for finding an element that is present in the array. + */ + @Test + void testSearcherFound() throws InterruptedException { + int[] array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Searcher searcher = new Searcher(array, 0, array.length, 5); + searcher.start(); + searcher.join(); + assertTrue(searcher.getResult(), "The element 5 should be found in the array."); + } + + /** + * Test for finding an element that is not present in the array. + */ + @Test + void testSearcherNotFound() throws InterruptedException { + int[] array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Searcher searcher = new Searcher(array, 0, array.length, 11); + searcher.start(); + searcher.join(); + assertFalse(searcher.getResult(), "The element 11 should not be found in the array."); + } + + /** + * Test for searching a segment of the array. + */ + @Test + void testSearcherSegmentFound() throws InterruptedException { + int[] array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Searcher searcher = new Searcher(array, 0, 5, 3); + searcher.start(); + searcher.join(); + assertTrue(searcher.getResult(), "The element 3 should be found in the segment."); + } + + /** + * Test for searching an empty array segment. + */ + @Test + void testSearcherEmptySegment() throws InterruptedException { + int[] array = {}; + Searcher searcher = new Searcher(array, 0, 0, 1); // Empty array + searcher.start(); + searcher.join(); + assertFalse(searcher.getResult(), "The element should not be found in an empty segment."); + } + + /** + * Test for searching with random numbers. + */ + @Test + void testSearcherRandomNumbers() throws InterruptedException { + int size = 200; + int[] array = new int[size]; + for (int i = 0; i < size; i++) { + array[i] = (int) (Math.random() * 100); + } + int target = array[(int) (Math.random() * size)]; // Randomly select a target that is present + Searcher searcher = new Searcher(array, 0, size, target); + searcher.start(); + searcher.join(); + assertTrue(searcher.getResult(), "The randomly selected target should be found in the array."); + } +} diff --git a/src/test/java/com/thealgorithms/searches/LowerBoundTest.java b/src/test/java/com/thealgorithms/searches/LowerBoundTest.java new file mode 100644 index 000000000000..30f4a5cb257c --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/LowerBoundTest.java @@ -0,0 +1,59 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class LowerBoundTest { + + /** + * Test finding the lower bound for an element present in the array. + */ + @Test + void testLowerBoundElementPresent() { + Integer[] array = {1, 2, 3, 4, 5}; + LowerBound lowerBound = new LowerBound(); + + // Test for a value that is present + assertEquals(2, lowerBound.find(array, 3), "Lower bound for 3 should be at index 2"); + assertEquals(0, lowerBound.find(array, 1), "Lower bound for 1 should be at index 0"); + assertEquals(4, lowerBound.find(array, 5), "Lower bound for 5 should be at index 4"); + } + + /** + * Test finding the lower bound for a value greater than the maximum element in the array. + */ + @Test + void testLowerBoundElementGreaterThanMax() { + Integer[] array = {1, 2, 3, 4, 5}; + LowerBound lowerBound = new LowerBound(); + + // Test for a value greater than the maximum + assertEquals(4, lowerBound.find(array, 6), "Lower bound for 6 should be at index 4"); + } + + /** + * Test finding the lower bound for a value less than the minimum element in the array. + */ + @Test + void testLowerBoundElementLessThanMin() { + Integer[] array = {1, 2, 3, 4, 5}; + LowerBound lowerBound = new LowerBound(); + + // Test for a value less than the minimum + assertEquals(0, lowerBound.find(array, 0), "Lower bound for 0 should be at index 0"); + } + + /** + * Test finding the lower bound for a non-existent value that falls between two elements. + */ + @Test + void testLowerBoundNonExistentValue() { + Integer[] array = {1, 2, 3, 4, 5}; + LowerBound lowerBound = new LowerBound(); + + // Test for a value that is not present + assertEquals(4, lowerBound.find(array, 7), "Lower bound for 7 should be at index 4"); + assertEquals(0, lowerBound.find(array, 0), "Lower bound for 0 should be at index 0"); + } +} diff --git a/src/test/java/com/thealgorithms/searches/MonteCarloTreeSearchTest.java b/src/test/java/com/thealgorithms/searches/MonteCarloTreeSearchTest.java new file mode 100644 index 000000000000..69c958f67a40 --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/MonteCarloTreeSearchTest.java @@ -0,0 +1,126 @@ +package com.thealgorithms.searches; + +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.Test; + +class MonteCarloTreeSearchTest { + + /** + * Test the creation of a node and its initial state. + */ + @Test + void testNodeCreation() { + MonteCarloTreeSearch.Node node = new MonteCarloTreeSearch().new Node(null, true); + assertNotNull(node, "Node should be created"); + assertTrue(node.childNodes.isEmpty(), "Child nodes should be empty upon creation"); + assertTrue(node.isPlayersTurn, "Initial turn should be player's turn"); + assertEquals(0, node.score, "Initial score should be zero"); + assertEquals(0, node.visitCount, "Initial visit count should be zero"); + } + + /** + * Test adding child nodes to a parent node. + */ + @Test + void testAddChildNodes() { + MonteCarloTreeSearch mcts = new MonteCarloTreeSearch(); + MonteCarloTreeSearch.Node parentNode = mcts.new Node(null, true); + + mcts.addChildNodes(parentNode, 5); + + assertEquals(5, parentNode.childNodes.size(), "Parent should have 5 child nodes"); + for (MonteCarloTreeSearch.Node child : parentNode.childNodes) { + assertFalse(child.isPlayersTurn, "Child node should not be player's turn"); + assertEquals(0, child.visitCount, "Child node visit count should be zero"); + } + } + + /** + * Test the UCT selection of a promising node. + */ + @Test + void testGetPromisingNode() { + MonteCarloTreeSearch mcts = new MonteCarloTreeSearch(); + MonteCarloTreeSearch.Node parentNode = mcts.new Node(null, true); + + // Create child nodes with different visit counts and scores + for (int i = 0; i < 3; i++) { + MonteCarloTreeSearch.Node child = mcts.new Node(parentNode, false); + child.visitCount = i + 1; + child.score = i * 2; + parentNode.childNodes.add(child); + } + + // Get promising node + MonteCarloTreeSearch.Node promisingNode = mcts.getPromisingNode(parentNode); + + // The child with the highest UCT value should be chosen. + assertNotNull(promisingNode, "Promising node should not be null"); + assertEquals(0, parentNode.childNodes.indexOf(promisingNode), "The first child should be the most promising"); + } + + /** + * Test simulation of random play and backpropagation. + */ + @Test + void testSimulateRandomPlay() { + MonteCarloTreeSearch mcts = new MonteCarloTreeSearch(); + MonteCarloTreeSearch.Node node = mcts.new Node(null, true); + node.visitCount = 10; // Simulating existing visits + + // Simulate random play + mcts.simulateRandomPlay(node); + + // Check visit count after simulation + assertEquals(11, node.visitCount, "Visit count should increase after simulation"); + + // Check if score is updated correctly + assertTrue(node.score >= 0 && node.score <= MonteCarloTreeSearch.WIN_SCORE, "Score should be between 0 and WIN_SCORE"); + } + + /** + * Test retrieving the winning node based on scores. + */ + @Test + void testGetWinnerNode() { + MonteCarloTreeSearch mcts = new MonteCarloTreeSearch(); + MonteCarloTreeSearch.Node parentNode = mcts.new Node(null, true); + + // Create child nodes with varying scores + MonteCarloTreeSearch.Node winningNode = mcts.new Node(parentNode, false); + winningNode.score = 10; // Highest score + parentNode.childNodes.add(winningNode); + + MonteCarloTreeSearch.Node losingNode = mcts.new Node(parentNode, false); + losingNode.score = 5; + parentNode.childNodes.add(losingNode); + + MonteCarloTreeSearch.Node anotherLosingNode = mcts.new Node(parentNode, false); + anotherLosingNode.score = 3; + parentNode.childNodes.add(anotherLosingNode); + + // Get the winning node + MonteCarloTreeSearch.Node winnerNode = mcts.getWinnerNode(parentNode); + + assertEquals(winningNode, winnerNode, "Winning node should have the highest score"); + } + + /** + * Test the full Monte Carlo Tree Search process. + */ + @Test + void testMonteCarloTreeSearch() { + MonteCarloTreeSearch mcts = new MonteCarloTreeSearch(); + MonteCarloTreeSearch.Node rootNode = mcts.new Node(null, true); + + // Execute MCTS and check the resulting node + MonteCarloTreeSearch.Node optimalNode = mcts.monteCarloTreeSearch(rootNode); + + assertNotNull(optimalNode, "MCTS should return a non-null optimal node"); + assertTrue(rootNode.childNodes.contains(optimalNode), "Optimal node should be a child of the root"); + } +} diff --git a/src/test/java/com/thealgorithms/searches/QuickSelectTest.java b/src/test/java/com/thealgorithms/searches/QuickSelectTest.java index dd04c85b76ae..cf160b0ff4b5 100644 --- a/src/test/java/com/thealgorithms/searches/QuickSelectTest.java +++ b/src/test/java/com/thealgorithms/searches/QuickSelectTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -225,7 +226,7 @@ private static List generateRandomCharacters(int n) { return RANDOM.ints(n, ASCII_A, ASCII_Z).mapToObj(i -> (char) i).collect(Collectors.toList()); } - private static > List getSortedCopyOfList(List list) { + private static > List getSortedCopyOfList(Collection list) { return list.stream().sorted().collect(Collectors.toList()); } } diff --git a/src/test/java/com/thealgorithms/searches/RandomSearchTest.java b/src/test/java/com/thealgorithms/searches/RandomSearchTest.java new file mode 100644 index 000000000000..0a1dbeafd888 --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/RandomSearchTest.java @@ -0,0 +1,87 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class RandomSearchTest { + + private RandomSearch randomSearch; + + @BeforeEach + void setUp() { + randomSearch = new RandomSearch(); + } + + @Test + void testElementFound() { + Integer[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Integer key = 5; + int index = randomSearch.find(array, key); + + assertNotEquals(-1, index, "Element should be found in the array."); + assertEquals(key, array[index], "Element found should match the key."); + } + + @Test + void testElementNotFound() { + Integer[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Integer key = 11; + int index = randomSearch.find(array, key); + + assertEquals(-1, index, "Element not present in the array should return -1."); + } + + @Test + void testEmptyArray() { + Integer[] emptyArray = {}; + Integer key = 5; + int index = randomSearch.find(emptyArray, key); + + assertEquals(-1, index, "Searching in an empty array should return -1."); + } + + @Test + void testSingleElementArrayFound() { + Integer[] array = {5}; + Integer key = 5; + int index = randomSearch.find(array, key); + + assertEquals(0, index, "The key should be found at index 0 in a single-element array."); + } + + @Test + void testSingleElementArrayNotFound() { + Integer[] array = {1}; + Integer key = 5; + int index = randomSearch.find(array, key); + + assertEquals(-1, index, "The key should not be found in a single-element array if it does not match."); + } + + @Test + void testDuplicateElementsFound() { + Integer[] array = {1, 2, 3, 4, 5, 5, 5, 7, 8, 9, 10}; + Integer key = 5; + int index = randomSearch.find(array, key); + + assertNotEquals(-1, index, "The key should be found in the array with duplicates."); + assertEquals(key, array[index], "The key found should be 5."); + } + + @Test + void testLargeArray() { + Integer[] largeArray = new Integer[1000]; + for (int i = 0; i < largeArray.length; i++) { + largeArray[i] = i + 1; // Fill with values 1 to 1000 + } + + Integer key = 500; + int index = randomSearch.find(largeArray, key); + + assertNotEquals(-1, index, "The key should be found in the large array."); + assertEquals(key, largeArray[index], "The key found should match 500."); + } +} diff --git a/src/test/java/com/thealgorithms/searches/SaddlebackSearchTest.java b/src/test/java/com/thealgorithms/searches/SaddlebackSearchTest.java new file mode 100644 index 000000000000..ec22cbf38152 --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/SaddlebackSearchTest.java @@ -0,0 +1,85 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class SaddlebackSearchTest { + + /** + * Test searching for an element that exists in the array. + */ + @Test + void testFindElementExists() { + int[][] arr = {{-10, -5, -3, 4, 9}, {-6, -2, 0, 5, 10}, {-4, -1, 1, 6, 12}, {2, 3, 7, 8, 13}, {100, 120, 130, 140, 150}}; + + int[] result = SaddlebackSearch.find(arr, arr.length - 1, 0, 4); + assertArrayEquals(new int[] {0, 3}, result, "Element 4 should be found at (0, 3)"); + } + + /** + * Test searching for an element that does not exist in the array. + */ + @Test + void testFindElementNotExists() { + int[][] arr = {{-10, -5, -3, 4, 9}, {-6, -2, 0, 5, 10}, {-4, -1, 1, 6, 12}, {2, 3, 7, 8, 13}, {100, 120, 130, 140, 150}}; + + int[] result = SaddlebackSearch.find(arr, arr.length - 1, 0, 1000); + assertArrayEquals(new int[] {-1, -1}, result, "Element 1000 should not be found"); + } + + /** + * Test searching for the smallest element in the array. + */ + @Test + void testFindSmallestElement() { + int[][] arr = {{-10, -5, -3, 4, 9}, {-6, -2, 0, 5, 10}, {-4, -1, 1, 6, 12}, {2, 3, 7, 8, 13}, {100, 120, 130, 140, 150}}; + + int[] result = SaddlebackSearch.find(arr, arr.length - 1, 0, -10); + assertArrayEquals(new int[] {0, 0}, result, "Element -10 should be found at (0, 0)"); + } + + /** + * Test searching for the largest element in the array. + */ + @Test + void testFindLargestElement() { + int[][] arr = {{-10, -5, -3, 4, 9}, {-6, -2, 0, 5, 10}, {-4, -1, 1, 6, 12}, {2, 3, 7, 8, 13}, {100, 120, 130, 140, 150}}; + + int[] result = SaddlebackSearch.find(arr, arr.length - 1, 0, 150); + assertArrayEquals(new int[] {4, 4}, result, "Element 150 should be found at (4, 4)"); + } + + /** + * Test searching in an empty array. + */ + @Test + void testFindInEmptyArray() { + int[][] arr = {}; + + assertThrows(IllegalArgumentException.class, () -> { SaddlebackSearch.find(arr, 0, 0, 4); }); + } + + /** + * Test searching in a single element array that matches the search key. + */ + @Test + void testFindSingleElementExists() { + int[][] arr = {{5}}; + + int[] result = SaddlebackSearch.find(arr, 0, 0, 5); + assertArrayEquals(new int[] {0, 0}, result, "Element 5 should be found at (0, 0)"); + } + + /** + * Test searching in a single element array that does not match the search key. + */ + @Test + void testFindSingleElementNotExists() { + int[][] arr = {{5}}; + + int[] result = SaddlebackSearch.find(arr, 0, 0, 10); + assertArrayEquals(new int[] {-1, -1}, result, "Element 10 should not be found in single element array"); + } +} diff --git a/src/test/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrixTest.java b/src/test/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrixTest.java new file mode 100644 index 000000000000..88227804b775 --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrixTest.java @@ -0,0 +1,66 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.Test; + +class SearchInARowAndColWiseSortedMatrixTest { + + private final SearchInARowAndColWiseSortedMatrix searcher = new SearchInARowAndColWiseSortedMatrix(); + + @Test + void testSearchValueExistsInMatrix() { + int[][] matrix = {{10, 20, 30, 40}, {15, 25, 35, 45}, {27, 29, 37, 48}, {32, 33, 39, 50}}; + int value = 29; + int[] expected = {2, 1}; // Row 2, Column 1 + assertArrayEquals(expected, searcher.search(matrix, value), "Value should be found in the matrix"); + } + + @Test + void testSearchValueNotExistsInMatrix() { + int[][] matrix = {{10, 20, 30, 40}, {15, 25, 35, 45}, {27, 29, 37, 48}, {32, 33, 39, 50}}; + int value = 100; + int[] expected = {-1, -1}; // Not found + assertArrayEquals(expected, searcher.search(matrix, value), "Value should not be found in the matrix"); + } + + @Test + void testSearchInEmptyMatrix() { + int[][] matrix = {}; + int value = 5; + int[] expected = {-1, -1}; // Not found + assertArrayEquals(expected, searcher.search(matrix, value), "Should return {-1, -1} for empty matrix"); + } + + @Test + void testSearchInSingleElementMatrixFound() { + int[][] matrix = {{5}}; + int value = 5; + int[] expected = {0, 0}; // Found at (0,0) + assertArrayEquals(expected, searcher.search(matrix, value), "Value should be found in single element matrix"); + } + + @Test + void testSearchInSingleElementMatrixNotFound() { + int[][] matrix = {{10}}; + int value = 5; + int[] expected = {-1, -1}; // Not found + assertArrayEquals(expected, searcher.search(matrix, value), "Should return {-1, -1} for value not found in single element matrix"); + } + + @Test + void testSearchInRowWiseSortedMatrix() { + int[][] matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; + int value = 6; + int[] expected = {1, 2}; // Found at (1, 2) + assertArrayEquals(expected, searcher.search(matrix, value), "Value should be found in the row-wise sorted matrix"); + } + + @Test + void testSearchInColWiseSortedMatrix() { + int[][] matrix = {{1, 4, 7}, {2, 5, 8}, {3, 6, 9}}; + int value = 5; + int[] expected = {1, 1}; // Found at (1, 1) + assertArrayEquals(expected, searcher.search(matrix, value), "Value should be found in the column-wise sorted matrix"); + } +} diff --git a/src/test/java/com/thealgorithms/searches/SquareRootBinarySearchTest.java b/src/test/java/com/thealgorithms/searches/SquareRootBinarySearchTest.java new file mode 100644 index 000000000000..f23f4ee83fc3 --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/SquareRootBinarySearchTest.java @@ -0,0 +1,57 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class SquareRootBinarySearchTest { + + @Test + void testPerfectSquare() { + long input = 16; + long expected = 4; + assertEquals(expected, SquareRootBinarySearch.squareRoot(input), "Square root of 16 should be 4"); + } + + @Test + void testNonPerfectSquare() { + long input = 15; + long expected = 3; + assertEquals(expected, SquareRootBinarySearch.squareRoot(input), "Square root of 15 should be 3"); + } + + @Test + void testZero() { + long input = 0; + long expected = 0; + assertEquals(expected, SquareRootBinarySearch.squareRoot(input), "Square root of 0 should be 0"); + } + + @Test + void testOne() { + long input = 1; + long expected = 1; + assertEquals(expected, SquareRootBinarySearch.squareRoot(input), "Square root of 1 should be 1"); + } + + @Test + void testLargeNumberPerfectSquare() { + long input = 1000000; + long expected = 1000; + assertEquals(expected, SquareRootBinarySearch.squareRoot(input), "Square root of 1000000 should be 1000"); + } + + @Test + void testLargeNumberNonPerfectSquare() { + long input = 999999; + long expected = 999; + assertEquals(expected, SquareRootBinarySearch.squareRoot(input), "Square root of 999999 should be 999"); + } + + @Test + void testNegativeInput() { + long input = -4; + long expected = 0; // Assuming the implementation should return 0 for negative input + assertEquals(expected, SquareRootBinarySearch.squareRoot(input), "Square root of negative number should return 0"); + } +} diff --git a/src/test/java/com/thealgorithms/searches/TernarySearchTest.java b/src/test/java/com/thealgorithms/searches/TernarySearchTest.java new file mode 100644 index 000000000000..367b595e55d9 --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/TernarySearchTest.java @@ -0,0 +1,81 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class TernarySearchTest { + + @Test + void testFindElementInSortedArray() { + Integer[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + TernarySearch search = new TernarySearch(); + + int index = search.find(arr, 5); + + assertEquals(4, index, "Should find the element 5 at index 4"); + } + + @Test + void testElementNotFound() { + Integer[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + TernarySearch search = new TernarySearch(); + + int index = search.find(arr, 11); + + assertEquals(-1, index, "Should return -1 for element 11 which is not present"); + } + + @Test + void testFindFirstElement() { + Integer[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + TernarySearch search = new TernarySearch(); + + int index = search.find(arr, 1); + + assertEquals(0, index, "Should find the first element 1 at index 0"); + } + + @Test + void testFindLastElement() { + Integer[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + TernarySearch search = new TernarySearch(); + + int index = search.find(arr, 10); + + assertEquals(9, index, "Should find the last element 10 at index 9"); + } + + @Test + void testFindInLargeArray() { + Integer[] arr = new Integer[1000]; + for (int i = 0; i < 1000; i++) { + arr[i] = i + 1; // Array from 1 to 1000 + } + TernarySearch search = new TernarySearch(); + + int index = search.find(arr, 500); + + assertEquals(499, index, "Should find element 500 at index 499"); + } + + @Test + void testNegativeNumbers() { + Integer[] arr = {-10, -5, -3, -1, 0, 1, 3, 5, 7, 10}; + TernarySearch search = new TernarySearch(); + + int index = search.find(arr, -3); + + assertEquals(2, index, "Should find the element -3 at index 2"); + } + + @Test + void testEdgeCaseEmptyArray() { + Integer[] arr = {}; + TernarySearch search = new TernarySearch(); + + int index = search.find(arr, 5); + + assertEquals(-1, index, "Should return -1 for an empty array"); + } +} diff --git a/src/test/java/com/thealgorithms/searches/TestSearchInARowAndColWiseSortedMatrix.java b/src/test/java/com/thealgorithms/searches/TestSearchInARowAndColWiseSortedMatrix.java index 014fb4bd24af..a56f79670cf3 100644 --- a/src/test/java/com/thealgorithms/searches/TestSearchInARowAndColWiseSortedMatrix.java +++ b/src/test/java/com/thealgorithms/searches/TestSearchInARowAndColWiseSortedMatrix.java @@ -1,27 +1,25 @@ -package com.thealgorithms.searches; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; - -import org.junit.jupiter.api.Test; - -public class TestSearchInARowAndColWiseSortedMatrix { - @Test - public void searchItem() { - int[][] matrix = {{3, 4, 5, 6, 7}, {8, 9, 10, 11, 12}, {14, 15, 16, 17, 18}, {23, 24, 25, 26, 27}, {30, 31, 32, 33, 34}}; - - var test = new SearchInARowAndColWiseSortedMatrix(); - int[] res = test.search(matrix, 16); - int[] expectedResult = {2, 2}; - assertArrayEquals(expectedResult, res); - } - - @Test - public void notFound() { - int[][] matrix = {{3, 4, 5, 6, 7}, {8, 9, 10, 11, 12}, {14, 15, 16, 17, 18}, {23, 24, 25, 26, 27}, {30, 31, 32, 33, 34}}; - - var test = new SearchInARowAndColWiseSortedMatrix(); - int[] res = test.search(matrix, 96); - int[] expectedResult = {-1, -1}; - assertArrayEquals(expectedResult, res); - } -} +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.Test; + +public class TestSearchInARowAndColWiseSortedMatrix { + @Test + public void searchItem() { + int[][] matrix = {{3, 4, 5, 6, 7}, {8, 9, 10, 11, 12}, {14, 15, 16, 17, 18}, {23, 24, 25, 26, 27}, {30, 31, 32, 33, 34}}; + var test = new SearchInARowAndColWiseSortedMatrix(); + int[] res = test.search(matrix, 16); + int[] expectedResult = {2, 2}; + assertArrayEquals(expectedResult, res); + } + + @Test + public void notFound() { + int[][] matrix = {{3, 4, 5, 6, 7}, {8, 9, 10, 11, 12}, {14, 15, 16, 17, 18}, {23, 24, 25, 26, 27}, {30, 31, 32, 33, 34}}; + var test = new SearchInARowAndColWiseSortedMatrix(); + int[] res = test.search(matrix, 96); + int[] expectedResult = {-1, -1}; + assertArrayEquals(expectedResult, res); + } +} diff --git a/src/test/java/com/thealgorithms/searches/UnionFindTest.java b/src/test/java/com/thealgorithms/searches/UnionFindTest.java new file mode 100644 index 000000000000..3cc025ff595c --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/UnionFindTest.java @@ -0,0 +1,81 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class UnionFindTest { + private UnionFind uf; + + @BeforeEach + void setUp() { + uf = new UnionFind(10); // Initialize with 10 elements + } + + @Test + void testInitialState() { + // Verify that each element is its own parent and rank is 0 + assertEquals("p [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] r [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n", uf.toString()); + assertEquals(10, uf.count(), "Initial count of disjoint sets should be 10."); + } + + @Test + void testUnionOperation() { + uf.union(0, 1); + uf.union(1, 2); + assertEquals(8, uf.count(), "Count should decrease after unions."); + assertEquals(0, uf.find(2), "Element 2 should point to root 0 after unions."); + } + + @Test + void testUnionWithRank() { + uf.union(0, 1); + uf.union(1, 2); // Make 0 the root of 2 + uf.union(3, 4); + uf.union(4, 5); // Make 3 the root of 5 + uf.union(0, 3); // Union two trees + + assertEquals(5, uf.count(), "Count should decrease after unions."); + assertEquals(0, uf.find(5), "Element 5 should point to root 0 after unions."); + } + + @Test + void testFindOperation() { + uf.union(2, 3); + uf.union(4, 5); + uf.union(3, 5); // Connect 2-3 and 4-5 + + assertEquals(2, uf.find(3), "Find operation should return the root of the set."); + assertEquals(2, uf.find(5), "Find operation should return the root of the set."); + } + + @Test + void testCountAfterMultipleUnions() { + uf.union(0, 1); + uf.union(2, 3); + uf.union(4, 5); + uf.union(1, 3); // Connect 0-1-2-3 + uf.union(5, 6); + + assertEquals(5, uf.count(), "Count should reflect the number of disjoint sets after multiple unions."); + } + + @Test + void testNoUnion() { + assertEquals(10, uf.count(), "Count should remain 10 if no unions are made."); + } + + @Test + void testUnionSameSet() { + uf.union(1, 2); + uf.union(1, 2); // Union same elements again + + assertEquals(9, uf.count(), "Count should not decrease if union is called on the same set."); + } + + @Test + void testFindOnSingleElement() { + assertEquals(7, uf.find(7), "Find on a single element should return itself."); + } +} diff --git a/src/test/java/com/thealgorithms/searches/UpperBoundTest.java b/src/test/java/com/thealgorithms/searches/UpperBoundTest.java new file mode 100644 index 000000000000..dc0cbdd97e19 --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/UpperBoundTest.java @@ -0,0 +1,99 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Random; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class UpperBoundTest { + + private UpperBound upperBound; + private Integer[] sortedArray; + + @BeforeEach + void setUp() { + upperBound = new UpperBound(); + + // Generate a sorted array of random integers for testing + Random random = new Random(); + int size = 100; + int maxElement = 100; + sortedArray = random.ints(size, 1, maxElement) + .distinct() // Ensure all elements are unique + .sorted() + .boxed() + .toArray(Integer[] ::new); + } + + @Test + void testUpperBoundFound() { + int key = sortedArray[sortedArray.length - 1] + 1; // Test with a key larger than max element + int index = upperBound.find(sortedArray, key); + + // The upper bound should be equal to the length of the array + assertEquals(sortedArray.length - 1, index, "Upper bound for a larger key should be the size of the array."); + } + + @Test + void testUpperBoundExactMatch() { + int key = sortedArray[sortedArray.length / 2]; // Choose a key from the middle of the array + int index = upperBound.find(sortedArray, key); + + // The index should point to the first element greater than the key + assertTrue(index < sortedArray.length, "Upper bound should not exceed array length."); + assertTrue(sortedArray[index] > key, "The element at the index should be greater than the key."); + } + + @Test + void testUpperBoundMultipleValues() { + Integer[] arrayWithDuplicates = new Integer[] {1, 1, 2, 3, 4, 4, 5, 6, 7, 8, 9}; // Test array with duplicates + int key = 4; + int index = upperBound.find(arrayWithDuplicates, key); + + assertTrue(index < arrayWithDuplicates.length, "Upper bound index should be valid."); + assertEquals(6, index, "The upper bound for 4 should be the index of the first 5."); + assertTrue(arrayWithDuplicates[index] > key, "Element at the upper bound index should be greater than the key."); + } + + @Test + void testUpperBoundLowerThanMin() { + int key = 0; // Test with a key lower than the minimum element + int index = upperBound.find(sortedArray, key); + + assertEquals(0, index, "Upper bound for a key lower than minimum should be 0."); + assertTrue(sortedArray[index] > key, "The element at index 0 should be greater than the key."); + } + + @Test + void testUpperBoundHigherThanMax() { + int key = sortedArray[sortedArray.length - 1] + 1; // Test with a key higher than maximum element + int index = upperBound.find(sortedArray, key); + + assertEquals(sortedArray.length - 1, index, "Upper bound for a key higher than maximum should be the size of the array."); + } + + @Test + void testUpperBoundEdgeCase() { + // Edge case: empty array + Integer[] emptyArray = {}; + int index = upperBound.find(emptyArray, 5); + + assertEquals(0, index, "Upper bound for an empty array should be 0."); + } + + @Test + void testUpperBoundSingleElementArray() { + Integer[] singleElementArray = {10}; + int index = upperBound.find(singleElementArray, 5); + + assertEquals(0, index, "Upper bound for 5 in a single element array should be 0."); + + index = upperBound.find(singleElementArray, 10); + assertEquals(0, index, "Upper bound for 10 in a single element array should be 0."); + + index = upperBound.find(singleElementArray, 15); + assertEquals(0, index, "Upper bound for 15 in a single element array should be 0."); + } +} diff --git a/src/test/java/com/thealgorithms/slidingwindow/LongestSubarrayWithSumLessOrEqualToKTest.java b/src/test/java/com/thealgorithms/slidingwindow/LongestSubarrayWithSumLessOrEqualToKTest.java new file mode 100644 index 000000000000..da282ab35ef3 --- /dev/null +++ b/src/test/java/com/thealgorithms/slidingwindow/LongestSubarrayWithSumLessOrEqualToKTest.java @@ -0,0 +1,22 @@ +package com.thealgorithms.slidingwindow; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the LongestSubarrayWithSumLessOrEqualToK algorithm. + */ +public class LongestSubarrayWithSumLessOrEqualToKTest { + + /** + * Tests for the longest subarray with a sum less than or equal to k. + */ + @Test + public void testLongestSubarrayWithSumLEK() { + assertEquals(3, LongestSubarrayWithSumLessOrEqualToK.longestSubarrayWithSumLEK(new int[] {1, 2, 3, 4}, 6)); // {1, 2, 3} + assertEquals(4, LongestSubarrayWithSumLessOrEqualToK.longestSubarrayWithSumLEK(new int[] {1, 2, 3, 4}, 10)); // {1, 2, 3, 4} + assertEquals(2, LongestSubarrayWithSumLessOrEqualToK.longestSubarrayWithSumLEK(new int[] {5, 1, 2, 3}, 5)); // {5} + assertEquals(0, LongestSubarrayWithSumLessOrEqualToK.longestSubarrayWithSumLEK(new int[] {1, 2, 3}, 0)); // No valid subarray + } +} diff --git a/src/test/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharactersTest.java b/src/test/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharactersTest.java new file mode 100644 index 000000000000..8638a707a3e2 --- /dev/null +++ b/src/test/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharactersTest.java @@ -0,0 +1,23 @@ +package com.thealgorithms.slidingwindow; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the LongestSubstringWithoutRepeatingCharacters class. + * + * @author (https://github.com/Chiefpatwal) + */ +public class LongestSubstringWithoutRepeatingCharactersTest { + + @Test + public void testLengthOfLongestSubstring() { + // Test cases for the lengthOfLongestSubstring method + assertEquals(3, LongestSubstringWithoutRepeatingCharacters.lengthOfLongestSubstring("abcabcbb")); + assertEquals(1, LongestSubstringWithoutRepeatingCharacters.lengthOfLongestSubstring("bbbbb")); + assertEquals(3, LongestSubstringWithoutRepeatingCharacters.lengthOfLongestSubstring("pwwkew")); + assertEquals(0, LongestSubstringWithoutRepeatingCharacters.lengthOfLongestSubstring("")); + assertEquals(5, LongestSubstringWithoutRepeatingCharacters.lengthOfLongestSubstring("abcde")); + } +} diff --git a/src/test/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarrayTest.java b/src/test/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarrayTest.java new file mode 100644 index 000000000000..aa3c2eae3294 --- /dev/null +++ b/src/test/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarrayTest.java @@ -0,0 +1,79 @@ +package com.thealgorithms.slidingwindow; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the MaxSumKSizeSubarray class. + * + * @author Your Name (https://github.com/Chiefpatwal) + */ +class MaxSumKSizeSubarrayTest { + + /** + * Test for the basic case of finding the maximum sum. + */ + @Test + void testMaxSumKSizeSubarray() { + int[] arr = {1, 2, 3, 4, 5}; + int k = 2; + int expectedMaxSum = 9; // 4 + 5 + assertEquals(expectedMaxSum, MaxSumKSizeSubarray.maxSumKSizeSubarray(arr, k)); + } + + /** + * Test for a different array and subarray size. + */ + @Test + void testMaxSumKSizeSubarrayWithDifferentValues() { + int[] arr = {2, 1, 5, 1, 3, 2}; + int k = 3; + int expectedMaxSum = 9; // 5 + 1 + 3 + assertEquals(expectedMaxSum, MaxSumKSizeSubarray.maxSumKSizeSubarray(arr, k)); + } + + /** + * Test for edge case with insufficient elements. + */ + @Test + void testMaxSumKSizeSubarrayWithInsufficientElements() { + int[] arr = {1, 2}; + int k = 3; // Not enough elements + int expectedMaxSum = -1; // Edge case + assertEquals(expectedMaxSum, MaxSumKSizeSubarray.maxSumKSizeSubarray(arr, k)); + } + + /** + * Test for large array. + */ + @Test + void testMaxSumKSizeSubarrayWithLargeArray() { + int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + int k = 5; + int expectedMaxSum = 40; // 6 + 7 + 8 + 9 + 10 + assertEquals(expectedMaxSum, MaxSumKSizeSubarray.maxSumKSizeSubarray(arr, k)); + } + + /** + * Test for array with negative numbers. + */ + @Test + void testMaxSumKSizeSubarrayWithNegativeNumbers() { + int[] arr = {-1, -2, -3, -4, -5}; + int k = 2; + int expectedMaxSum = -3; // -1 + -2 + assertEquals(expectedMaxSum, MaxSumKSizeSubarray.maxSumKSizeSubarray(arr, k)); + } + + /** + * Test for the case where k equals the array length. + */ + @Test + void testMaxSumKSizeSubarrayWithKEqualToArrayLength() { + int[] arr = {1, 2, 3, 4, 5}; + int k = 5; + int expectedMaxSum = 15; // 1 + 2 + 3 + 4 + 5 + assertEquals(expectedMaxSum, MaxSumKSizeSubarray.maxSumKSizeSubarray(arr, k)); + } +} diff --git a/src/test/java/com/thealgorithms/slidingwindow/MinSumKSizeSubarrayTest.java b/src/test/java/com/thealgorithms/slidingwindow/MinSumKSizeSubarrayTest.java new file mode 100644 index 000000000000..4656c8baf327 --- /dev/null +++ b/src/test/java/com/thealgorithms/slidingwindow/MinSumKSizeSubarrayTest.java @@ -0,0 +1,79 @@ +package com.thealgorithms.slidingwindow; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the MinSumKSizeSubarray class. + * + * @author Rashi Dashore (https://github.com/rashi07dashore) + */ +class MinSumKSizeSubarrayTest { + + /** + * Test for the basic case of finding the minimum sum. + */ + @Test + void testMinSumKSizeSubarray() { + int[] arr = {2, 1, 5, 1, 3, 2}; + int k = 3; + int expectedMinSum = 6; // Corrected: Minimum sum of a subarray of size 3 + assertEquals(expectedMinSum, MinSumKSizeSubarray.minSumKSizeSubarray(arr, k)); + } + + /** + * Test for a different array and subarray size. + */ + @Test + void testMinSumKSizeSubarrayWithDifferentValues() { + int[] arr = {1, 2, 3, 4, 5}; + int k = 2; + int expectedMinSum = 3; // 1 + 2 + assertEquals(expectedMinSum, MinSumKSizeSubarray.minSumKSizeSubarray(arr, k)); + } + + /** + * Test for edge case with insufficient elements. + */ + @Test + void testMinSumKSizeSubarrayWithInsufficientElements() { + int[] arr = {1, 2}; + int k = 3; // Not enough elements + int expectedMinSum = -1; // Edge case + assertEquals(expectedMinSum, MinSumKSizeSubarray.minSumKSizeSubarray(arr, k)); + } + + /** + * Test for large array. + */ + @Test + void testMinSumKSizeSubarrayWithLargeArray() { + int[] arr = {5, 4, 3, 2, 1, 0, -1, -2, -3, -4}; + int k = 5; + int expectedMinSum = -10; // -1 + -2 + -3 + -4 + 0 + assertEquals(expectedMinSum, MinSumKSizeSubarray.minSumKSizeSubarray(arr, k)); + } + + /** + * Test for array with negative numbers. + */ + @Test + void testMinSumKSizeSubarrayWithNegativeNumbers() { + int[] arr = {-1, -2, -3, -4, -5}; + int k = 2; + int expectedMinSum = -9; // -4 + -5 + assertEquals(expectedMinSum, MinSumKSizeSubarray.minSumKSizeSubarray(arr, k)); + } + + /** + * Test for the case where k equals the array length. + */ + @Test + void testMinSumKSizeSubarrayWithKEqualToArrayLength() { + int[] arr = {1, 2, 3, 4, 5}; + int k = 5; + int expectedMinSum = 15; // 1 + 2 + 3 + 4 + 5 + assertEquals(expectedMinSum, MinSumKSizeSubarray.minSumKSizeSubarray(arr, k)); + } +} diff --git a/src/test/java/com/thealgorithms/sorts/AdaptiveMergeSortTest.java b/src/test/java/com/thealgorithms/sorts/AdaptiveMergeSortTest.java new file mode 100644 index 000000000000..9d94b165d81b --- /dev/null +++ b/src/test/java/com/thealgorithms/sorts/AdaptiveMergeSortTest.java @@ -0,0 +1,53 @@ +package com.thealgorithms.sorts; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.Test; + +public class AdaptiveMergeSortTest { + + @Test + public void testSortIntegers() { + AdaptiveMergeSort adaptiveMergeSort = new AdaptiveMergeSort(); + Integer[] input = {4, 23, 6, 78, 1, 54, 231, 9, 12}; + Integer[] expected = {1, 4, 6, 9, 12, 23, 54, 78, 231}; + Integer[] result = adaptiveMergeSort.sort(input); + assertArrayEquals(expected, result); + } + + @Test + public void testSortStrings() { + AdaptiveMergeSort adaptiveMergeSort = new AdaptiveMergeSort(); + String[] input = {"c", "a", "e", "b", "d"}; + String[] expected = {"a", "b", "c", "d", "e"}; + String[] result = adaptiveMergeSort.sort(input); + assertArrayEquals(expected, result); + } + + @Test + public void testSortWithDuplicates() { + AdaptiveMergeSort adaptiveMergeSort = new AdaptiveMergeSort(); + Integer[] input = {1, 3, 2, 2, 5, 4}; + Integer[] expected = {1, 2, 2, 3, 4, 5}; + Integer[] result = adaptiveMergeSort.sort(input); + assertArrayEquals(expected, result); + } + + @Test + public void testSortEmptyArray() { + AdaptiveMergeSort adaptiveMergeSort = new AdaptiveMergeSort(); + Integer[] input = {}; + Integer[] expected = {}; + Integer[] result = adaptiveMergeSort.sort(input); + assertArrayEquals(expected, result); + } + + @Test + public void testSortSingleElement() { + AdaptiveMergeSort adaptiveMergeSort = new AdaptiveMergeSort(); + Integer[] input = {42}; + Integer[] expected = {42}; + Integer[] result = adaptiveMergeSort.sort(input); + assertArrayEquals(expected, result); + } +} diff --git a/src/test/java/com/thealgorithms/sorts/DarkSortTest.java b/src/test/java/com/thealgorithms/sorts/DarkSortTest.java new file mode 100644 index 000000000000..1df077e2ad74 --- /dev/null +++ b/src/test/java/com/thealgorithms/sorts/DarkSortTest.java @@ -0,0 +1,74 @@ +package com.thealgorithms.sorts; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +class DarkSortTest { + + @Test + void testSortWithIntegers() { + Integer[] unsorted = {5, 3, 8, 6, 2, 7, 4, 1}; + Integer[] expected = {1, 2, 3, 4, 5, 6, 7, 8}; + + DarkSort darkSort = new DarkSort(); + Integer[] sorted = darkSort.sort(unsorted); + + assertArrayEquals(expected, sorted); + } + + @Test + void testEmptyArray() { + Integer[] unsorted = {}; + Integer[] expected = {}; + + DarkSort darkSort = new DarkSort(); + Integer[] sorted = darkSort.sort(unsorted); + + assertArrayEquals(expected, sorted); + } + + @Test + void testSingleElementArray() { + Integer[] unsorted = {42}; + Integer[] expected = {42}; + + DarkSort darkSort = new DarkSort(); + Integer[] sorted = darkSort.sort(unsorted); + + assertArrayEquals(expected, sorted); + } + + @Test + void testAlreadySortedArray() { + Integer[] unsorted = {1, 2, 3, 4, 5}; + Integer[] expected = {1, 2, 3, 4, 5}; + + DarkSort darkSort = new DarkSort(); + Integer[] sorted = darkSort.sort(unsorted); + + assertArrayEquals(expected, sorted); + } + + @Test + void testDuplicateElementsArray() { + Integer[] unsorted = {4, 2, 7, 2, 1, 4}; + Integer[] expected = {1, 2, 2, 4, 4, 7}; + + DarkSort darkSort = new DarkSort(); + Integer[] sorted = darkSort.sort(unsorted); + + assertArrayEquals(expected, sorted); + } + + @Test + void testNullArray() { + Integer[] unsorted = null; + + DarkSort darkSort = new DarkSort(); + Integer[] sorted = darkSort.sort(unsorted); + + assertNull(sorted, "Sorting a null array should return null"); + } +} diff --git a/src/test/java/com/thealgorithms/sorts/StalinSortTest.java b/src/test/java/com/thealgorithms/sorts/StalinSortTest.java new file mode 100644 index 000000000000..ee9c61379527 --- /dev/null +++ b/src/test/java/com/thealgorithms/sorts/StalinSortTest.java @@ -0,0 +1,53 @@ +package com.thealgorithms.sorts; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.Test; + +public class StalinSortTest { + + @Test + public void testSortIntegers() { + StalinSort stalinSort = new StalinSort(); + Integer[] input = {4, 23, 6, 78, 1, 54, 231, 9, 12}; + Integer[] expected = {4, 23, 78, 231}; + Integer[] result = stalinSort.sort(input); + assertArrayEquals(expected, result); + } + + @Test + public void testSortStrings() { + StalinSort stalinSort = new StalinSort(); + String[] input = {"c", "a", "e", "b", "d"}; + String[] expected = {"c", "e"}; + String[] result = stalinSort.sort(input); + assertArrayEquals(expected, result); + } + + @Test + public void testSortWithDuplicates() { + StalinSort stalinSort = new StalinSort(); + Integer[] input = {1, 3, 2, 2, 5, 4}; + Integer[] expected = {1, 3, 5}; + Integer[] result = stalinSort.sort(input); + assertArrayEquals(expected, result); + } + + @Test + public void testSortEmptyArray() { + StalinSort stalinSort = new StalinSort(); + Integer[] input = {}; + Integer[] expected = {}; + Integer[] result = stalinSort.sort(input); + assertArrayEquals(expected, result); + } + + @Test + public void testSortSingleElement() { + StalinSort stalinSort = new StalinSort(); + Integer[] input = {42}; + Integer[] expected = {42}; + Integer[] result = stalinSort.sort(input); + assertArrayEquals(expected, result); + } +} diff --git a/src/test/java/com/thealgorithms/stacks/CelebrityFinderTest.java b/src/test/java/com/thealgorithms/stacks/CelebrityFinderTest.java new file mode 100644 index 000000000000..da0217940c2c --- /dev/null +++ b/src/test/java/com/thealgorithms/stacks/CelebrityFinderTest.java @@ -0,0 +1,41 @@ +package com.thealgorithms.stacks; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class CelebrityFinderTest { + + @ParameterizedTest + @MethodSource("providePartyMatrices") + public void testCelebrityFinder(int[][] party, int expected) { + assertEquals(expected, CelebrityFinder.findCelebrity(party)); + } + + private static Stream providePartyMatrices() { + return Stream.of( + // Test case 1: Celebrity exists + Arguments.of(new int[][] {{0, 1, 1}, {0, 0, 1}, {0, 0, 0}}, 2), + + // Test case 2: No celebrity + Arguments.of(new int[][] {{0, 1, 0}, {1, 0, 1}, {1, 1, 0}}, -1), + + // Test case 3: Everyone knows each other, no celebrity + Arguments.of(new int[][] {{0, 1, 1}, {1, 0, 1}, {1, 1, 0}}, -1), + + // Test case 4: Single person, they are trivially a celebrity + Arguments.of(new int[][] {{0}}, 0), + + // Test case 5: All know the last person, and they know no one + Arguments.of(new int[][] {{0, 1, 1, 1}, {0, 0, 1, 1}, {0, 0, 0, 1}, {0, 0, 0, 0}}, 3), + + // Test case 6: Larger party with no celebrity + Arguments.of(new int[][] {{0, 1, 1, 0}, {1, 0, 0, 1}, {0, 1, 0, 1}, {1, 1, 1, 0}}, -1), + + // Test case 7: Celebrity at the start of the matrix + Arguments.of(new int[][] {{0, 0, 0}, {1, 0, 1}, {1, 1, 0}}, 0)); + } +} diff --git a/src/test/java/com/thealgorithms/stacks/GreatestElementConstantTimeTest.java b/src/test/java/com/thealgorithms/stacks/GreatestElementConstantTimeTest.java new file mode 100644 index 000000000000..080592dc68e8 --- /dev/null +++ b/src/test/java/com/thealgorithms/stacks/GreatestElementConstantTimeTest.java @@ -0,0 +1,70 @@ +package com.thealgorithms.stacks; + +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 java.util.NoSuchElementException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class GreatestElementConstantTimeTest { + + private GreatestElementConstantTime constantTime; + + @BeforeEach + public void setConstantTime() { + constantTime = new GreatestElementConstantTime(); + } + + @Test + public void testMaxAtFirst() { + constantTime.push(1); + constantTime.push(10); + constantTime.push(20); + constantTime.push(5); + assertEquals(20, constantTime.getMaximumElement()); + } + + @Test + public void testMinTwo() { + constantTime.push(5); + constantTime.push(10); + constantTime.push(20); + constantTime.push(1); + assertEquals(20, constantTime.getMaximumElement()); + constantTime.pop(); + constantTime.pop(); + assertEquals(10, constantTime.getMaximumElement()); + } + + @Test + public void testNullMax() { + constantTime.push(10); + constantTime.push(20); + constantTime.pop(); + constantTime.pop(); + assertNull(constantTime.getMaximumElement()); + } + + @Test + public void testBlankHandle() { + constantTime.push(10); + constantTime.push(1); + constantTime.pop(); + constantTime.pop(); + assertThrows(NoSuchElementException.class, () -> constantTime.pop()); + } + + @Test + public void testPushPopAfterEmpty() { + constantTime.push(10); + constantTime.push(1); + constantTime.pop(); + constantTime.pop(); + constantTime.push(5); + assertEquals(5, constantTime.getMaximumElement()); + constantTime.push(1); + assertEquals(5, constantTime.getMaximumElement()); + } +} diff --git a/src/test/java/com/thealgorithms/stacks/MinStackUsingSingleStackTest.java b/src/test/java/com/thealgorithms/stacks/MinStackUsingSingleStackTest.java new file mode 100644 index 000000000000..90887294638f --- /dev/null +++ b/src/test/java/com/thealgorithms/stacks/MinStackUsingSingleStackTest.java @@ -0,0 +1,66 @@ +package com.thealgorithms.stacks; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.EmptyStackException; +import org.junit.jupiter.api.Test; + +public class MinStackUsingSingleStackTest { + + @Test + public void testBasicOperations() { + MinStackUsingSingleStack minStack = new MinStackUsingSingleStack(); + + minStack.push(3); + minStack.push(5); + assertEquals(3, minStack.getMin(), "Minimum should be 3"); + + minStack.push(2); + minStack.push(1); + assertEquals(1, minStack.getMin(), "Minimum should be 1"); + + minStack.pop(); + assertEquals(2, minStack.getMin(), "Minimum should be 2"); + + minStack.pop(); + assertEquals(3, minStack.getMin(), "Minimum should be 3"); + } + + @Test + public void testTopElement() { + MinStackUsingSingleStack minStack = new MinStackUsingSingleStack(); + + minStack.push(8); + minStack.push(10); + assertEquals(10, minStack.top(), "Top element should be 10"); + + minStack.pop(); + assertEquals(8, minStack.top(), "Top element should be 8"); + } + + @Test + public void testGetMinAfterPops() { + MinStackUsingSingleStack minStack = new MinStackUsingSingleStack(); + + minStack.push(5); + minStack.push(3); + minStack.push(7); + + assertEquals(3, minStack.getMin(), "Minimum should be 3"); + + minStack.pop(); // Popping 7 + assertEquals(3, minStack.getMin(), "Minimum should still be 3"); + + minStack.pop(); // Popping 3 + assertEquals(5, minStack.getMin(), "Minimum should now be 5"); + } + + @Test + public void testEmptyStack() { + MinStackUsingSingleStack minStack = new MinStackUsingSingleStack(); + + assertThrows(EmptyStackException.class, minStack::top, "Should throw exception on top()"); + assertThrows(EmptyStackException.class, minStack::getMin, "Should throw exception on getMin()"); + } +} diff --git a/src/test/java/com/thealgorithms/stacks/MinStackUsingTwoStacksTest.java b/src/test/java/com/thealgorithms/stacks/MinStackUsingTwoStacksTest.java new file mode 100644 index 000000000000..e5deb17e9a8f --- /dev/null +++ b/src/test/java/com/thealgorithms/stacks/MinStackUsingTwoStacksTest.java @@ -0,0 +1,38 @@ +package com.thealgorithms.stacks; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class MinStackUsingTwoStacksTest { + + @Test + public void testMinStackOperations() { + MinStackUsingTwoStacks minStack = new MinStackUsingTwoStacks(); + minStack.push(3); + minStack.push(5); + assertEquals(3, minStack.getMin()); + + minStack.push(2); + minStack.push(1); + assertEquals(1, minStack.getMin()); + + minStack.pop(); + assertEquals(2, minStack.getMin()); + } + + @Test + public void testMinStackOperations2() { + MinStackUsingTwoStacks minStack = new MinStackUsingTwoStacks(); + minStack.push(3); + minStack.push(5); + assertEquals(3, minStack.getMin()); + + minStack.push(2); + minStack.push(1); + assertEquals(1, minStack.getMin()); + + minStack.pop(); + assertEquals(2, minStack.getMin()); + } +} diff --git a/src/test/java/com/thealgorithms/stacks/PalindromeWithStackTest.java b/src/test/java/com/thealgorithms/stacks/PalindromeWithStackTest.java new file mode 100644 index 000000000000..47b21d5e9e9c --- /dev/null +++ b/src/test/java/com/thealgorithms/stacks/PalindromeWithStackTest.java @@ -0,0 +1,77 @@ +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.BeforeEach; +import org.junit.jupiter.api.Test; + +public class PalindromeWithStackTest { + + private PalindromeWithStack palindromeChecker; + + @BeforeEach + public void setUp() { + palindromeChecker = new PalindromeWithStack(); + } + + @Test + public void testValidOne() { + String testString = "Racecar"; + assertTrue(palindromeChecker.checkPalindrome(testString)); + } + + @Test + public void testInvalidOne() { + String testString = "James"; + assertFalse(palindromeChecker.checkPalindrome(testString)); + } + + @Test + public void testValidTwo() { + String testString = "madam"; + assertTrue(palindromeChecker.checkPalindrome(testString)); + } + + @Test + public void testInvalidTwo() { + String testString = "pantry"; + assertFalse(palindromeChecker.checkPalindrome(testString)); + } + + @Test + public void testValidThree() { + String testString = "RaDar"; + assertTrue(palindromeChecker.checkPalindrome(testString)); + } + + @Test + public void testInvalidThree() { + String testString = "Win"; + assertFalse(palindromeChecker.checkPalindrome(testString)); + } + + @Test + public void testBlankString() { + String testString = ""; + assertTrue(palindromeChecker.checkPalindrome(testString)); + } + + @Test + public void testStringWithNumbers() { + String testString = "12321"; + assertTrue(palindromeChecker.checkPalindrome(testString)); + } + + @Test + public void testStringWithNumbersTwo() { + String testString = "12325"; + assertFalse(palindromeChecker.checkPalindrome(testString)); + } + + @Test + public void testStringWithNumbersAndLetters() { + String testString = "po454op"; + assertTrue(palindromeChecker.checkPalindrome(testString)); + } +} diff --git a/src/test/java/com/thealgorithms/stacks/PostfixEvaluatorTest.java b/src/test/java/com/thealgorithms/stacks/PostfixEvaluatorTest.java new file mode 100644 index 000000000000..882fe644ccd5 --- /dev/null +++ b/src/test/java/com/thealgorithms/stacks/PostfixEvaluatorTest.java @@ -0,0 +1,27 @@ +package com.thealgorithms.stacks; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.EmptyStackException; +import org.junit.jupiter.api.Test; + +public class PostfixEvaluatorTest { + + @Test + public void testValidExpressions() { + assertEquals(22, PostfixEvaluator.evaluatePostfix("5 6 + 2 *")); + assertEquals(27, PostfixEvaluator.evaluatePostfix("7 2 + 3 *")); + assertEquals(3, PostfixEvaluator.evaluatePostfix("10 5 / 1 +")); + } + + @Test + public void testInvalidExpression() { + assertThrows(EmptyStackException.class, () -> PostfixEvaluator.evaluatePostfix("5 +")); + } + + @Test + public void testMoreThanOneStackSizeAfterEvaluation() { + assertThrows(IllegalArgumentException.class, () -> PostfixEvaluator.evaluatePostfix("5 6 + 2 * 3")); + } +} diff --git a/src/test/java/com/thealgorithms/stacks/PrefixEvaluatorTest.java b/src/test/java/com/thealgorithms/stacks/PrefixEvaluatorTest.java new file mode 100644 index 000000000000..e2faa61955b3 --- /dev/null +++ b/src/test/java/com/thealgorithms/stacks/PrefixEvaluatorTest.java @@ -0,0 +1,27 @@ +package com.thealgorithms.stacks; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.EmptyStackException; +import org.junit.jupiter.api.Test; + +public class PrefixEvaluatorTest { + + @Test + public void testValidExpressions() { + assertEquals(10, PrefixEvaluator.evaluatePrefix("+ * 2 3 4")); + assertEquals(5, PrefixEvaluator.evaluatePrefix("- + 7 3 5")); + assertEquals(6, PrefixEvaluator.evaluatePrefix("/ * 3 2 1")); + } + + @Test + public void testInvalidExpression() { + assertThrows(EmptyStackException.class, () -> PrefixEvaluator.evaluatePrefix("+ 3")); + } + + @Test + public void testMoreThanOneStackSizeAfterEvaluation() { + assertThrows(IllegalArgumentException.class, () -> PrefixEvaluator.evaluatePrefix("+ 3 4 5")); + } +} diff --git a/src/test/java/com/thealgorithms/stacks/SmallestElementConstantTimeTest.java b/src/test/java/com/thealgorithms/stacks/SmallestElementConstantTimeTest.java new file mode 100644 index 000000000000..b5eda9e8cb46 --- /dev/null +++ b/src/test/java/com/thealgorithms/stacks/SmallestElementConstantTimeTest.java @@ -0,0 +1,69 @@ +package com.thealgorithms.stacks; + +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 java.util.NoSuchElementException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SmallestElementConstantTimeTest { + + private SmallestElementConstantTime sect; + + @BeforeEach + public void setSect() { + sect = new SmallestElementConstantTime(); + } + + @Test + public void testMinAtFirst() { + sect.push(1); + sect.push(10); + sect.push(20); + sect.push(5); + assertEquals(1, sect.getMinimumElement()); + } + + @Test + public void testMinTwo() { + sect.push(5); + sect.push(10); + sect.push(20); + sect.push(1); + assertEquals(1, sect.getMinimumElement()); + sect.pop(); + assertEquals(5, sect.getMinimumElement()); + } + + @Test + public void testNullMin() { + sect.push(10); + sect.push(20); + sect.pop(); + sect.pop(); + assertNull(sect.getMinimumElement()); + } + + @Test + public void testBlankHandle() { + sect.push(10); + sect.push(1); + sect.pop(); + sect.pop(); + assertThrows(NoSuchElementException.class, () -> sect.pop()); + } + + @Test + public void testPushPopAfterEmpty() { + sect.push(10); + sect.push(1); + sect.pop(); + sect.pop(); + sect.push(5); + assertEquals(5, sect.getMinimumElement()); + sect.push(1); + assertEquals(1, sect.getMinimumElement()); + } +} diff --git a/src/test/java/com/thealgorithms/stacks/SortStackTest.java b/src/test/java/com/thealgorithms/stacks/SortStackTest.java new file mode 100644 index 000000000000..b9f2f1b6f106 --- /dev/null +++ b/src/test/java/com/thealgorithms/stacks/SortStackTest.java @@ -0,0 +1,77 @@ +package com.thealgorithms.stacks; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Stack; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SortStackTest { + + private Stack stack; + + @BeforeEach + public void setUp() { + stack = new Stack<>(); + } + + @Test + public void testSortEmptyStack() { + SortStack.sortStack(stack); + assertTrue(stack.isEmpty()); // An empty stack should remain empty + } + + @Test + public void testSortSingleElementStack() { + stack.push(10); + SortStack.sortStack(stack); + assertEquals(1, stack.size()); + assertEquals(10, (int) stack.peek()); // Single element should remain unchanged + } + + @Test + public void testSortAlreadySortedStack() { + stack.push(1); + stack.push(2); + stack.push(3); + stack.push(4); + SortStack.sortStack(stack); + + assertEquals(4, stack.size()); + assertEquals(4, (int) stack.pop()); + assertEquals(3, (int) stack.pop()); + assertEquals(2, (int) stack.pop()); + assertEquals(1, (int) stack.pop()); + } + + @Test + public void testSortUnsortedStack() { + stack.push(3); + stack.push(1); + stack.push(4); + stack.push(2); + SortStack.sortStack(stack); + + assertEquals(4, stack.size()); + assertEquals(4, (int) stack.pop()); + assertEquals(3, (int) stack.pop()); + assertEquals(2, (int) stack.pop()); + assertEquals(1, (int) stack.pop()); + } + + @Test + public void testSortWithDuplicateElements() { + stack.push(3); + stack.push(1); + stack.push(3); + stack.push(2); + SortStack.sortStack(stack); + + assertEquals(4, stack.size()); + assertEquals(3, (int) stack.pop()); + assertEquals(3, (int) stack.pop()); + assertEquals(2, (int) stack.pop()); + assertEquals(1, (int) stack.pop()); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/stacks/LinkedListStackTest.java b/src/test/java/com/thealgorithms/stacks/StackUsingTwoQueuesTest.java similarity index 62% rename from src/test/java/com/thealgorithms/datastructures/stacks/LinkedListStackTest.java rename to src/test/java/com/thealgorithms/stacks/StackUsingTwoQueuesTest.java index 8c3689a79729..a7e24421e682 100644 --- a/src/test/java/com/thealgorithms/datastructures/stacks/LinkedListStackTest.java +++ b/src/test/java/com/thealgorithms/stacks/StackUsingTwoQueuesTest.java @@ -1,20 +1,20 @@ -package com.thealgorithms.datastructures.stacks; +package com.thealgorithms.stacks; 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 java.util.NoSuchElementException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class LinkedListStackTest { +public class StackUsingTwoQueuesTest { - private LinkedListStack stack; + private StackUsingTwoQueues stack; @BeforeEach public void setUp() { - stack = new LinkedListStack(); + stack = new StackUsingTwoQueues(); } @Test @@ -22,9 +22,7 @@ public void testPushAndPeek() { stack.push(1); stack.push(2); stack.push(3); - assertEquals(3, stack.peek()); - assertEquals(3, stack.getSize()); } @Test @@ -32,40 +30,41 @@ public void testPop() { stack.push(1); stack.push(2); stack.push(3); - assertEquals(3, stack.pop()); assertEquals(2, stack.pop()); assertEquals(1, stack.pop()); - assertTrue(stack.isEmpty()); - } - - @Test - public void testPopEmptyStack() { - org.junit.jupiter.api.Assertions.assertThrows(NoSuchElementException.class, () -> stack.pop()); } @Test - public void testPeekEmptyStack() { - org.junit.jupiter.api.Assertions.assertThrows(NoSuchElementException.class, () -> stack.peek()); + public void testPeek() { + stack.push(10); + stack.push(20); + assertEquals(20, stack.peek()); + stack.pop(); + assertEquals(10, stack.peek()); } @Test public void testIsEmpty() { assertTrue(stack.isEmpty()); - stack.push(1); assertFalse(stack.isEmpty()); - stack.pop(); assertTrue(stack.isEmpty()); } @Test - public void testToString() { + public void testSize() { + assertEquals(0, stack.size()); stack.push(1); stack.push(2); - stack.push(3); + assertEquals(2, stack.size()); + stack.pop(); + assertEquals(1, stack.size()); + } - assertEquals("3->2->1", stack.toString()); + @Test + public void testPeekEmptyStack() { + assertNull(stack.peek()); } } diff --git a/src/test/java/com/thealgorithms/strings/LongestCommonPrefixTest.java b/src/test/java/com/thealgorithms/strings/LongestCommonPrefixTest.java new file mode 100644 index 000000000000..580a2726d285 --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/LongestCommonPrefixTest.java @@ -0,0 +1,73 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class LongestCommonPrefixTest { + + private final LongestCommonPrefix longestCommonPrefix = new LongestCommonPrefix(); + + @Test + public void testCommonPrefix() { + String[] input = {"flower", "flow", "flight"}; + String expected = "fl"; + assertEquals(expected, longestCommonPrefix.longestCommonPrefix(input)); + } + + @Test + public void testNoCommonPrefix() { + String[] input = {"dog", "racecar", "car"}; + String expected = ""; + assertEquals(expected, longestCommonPrefix.longestCommonPrefix(input)); + } + + @Test + public void testEmptyArray() { + String[] input = {}; + String expected = ""; + assertEquals(expected, longestCommonPrefix.longestCommonPrefix(input)); + } + + @Test + public void testNullArray() { + String[] input = null; + String expected = ""; + assertEquals(expected, longestCommonPrefix.longestCommonPrefix(input)); + } + + @Test + public void testSingleString() { + String[] input = {"single"}; + String expected = "single"; + assertEquals(expected, longestCommonPrefix.longestCommonPrefix(input)); + } + + @Test + public void testCommonPrefixWithDifferentLengths() { + String[] input = {"ab", "a"}; + String expected = "a"; + assertEquals(expected, longestCommonPrefix.longestCommonPrefix(input)); + } + + @Test + public void testAllSameStrings() { + String[] input = {"test", "test", "test"}; + String expected = "test"; + assertEquals(expected, longestCommonPrefix.longestCommonPrefix(input)); + } + + @Test + public void testPrefixAtEnd() { + String[] input = {"abcde", "abcfgh", "abcmnop"}; + String expected = "abc"; + assertEquals(expected, longestCommonPrefix.longestCommonPrefix(input)); + } + + @Test + public void testMixedCase() { + String[] input = {"Flower", "flow", "flight"}; + String expected = ""; + assertEquals(expected, longestCommonPrefix.longestCommonPrefix(input)); + } +} diff --git a/src/test/java/com/thealgorithms/strings/ReverseStringTest.java b/src/test/java/com/thealgorithms/strings/ReverseStringTest.java index 501f702976ec..08f5fb586d82 100644 --- a/src/test/java/com/thealgorithms/strings/ReverseStringTest.java +++ b/src/test/java/com/thealgorithms/strings/ReverseStringTest.java @@ -25,4 +25,10 @@ public void testReverseString(String input, String expectedOutput) { public void testReverseString2(String input, String expectedOutput) { assertEquals(expectedOutput, ReverseString.reverse2(input)); } + + @ParameterizedTest + @MethodSource("testCases") + public void testReverseString3(String input, String expectedOutput) { + assertEquals(expectedOutput, ReverseString.reverse3(input)); + } } diff --git a/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java b/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java index 22deb4b14d3c..2b6884c91c8f 100644 --- a/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java +++ b/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java @@ -10,15 +10,18 @@ public class ValidParenthesesTest { @Test void testOne() { assertTrue(ValidParentheses.isValid("()")); + assertTrue(ValidParentheses.isValidParentheses("()")); } @Test void testTwo() { assertTrue(ValidParentheses.isValid("()[]{}")); + assertTrue(ValidParentheses.isValidParentheses("()[]{}")); } @Test void testThree() { assertFalse(ValidParentheses.isValid("(]")); + assertFalse(ValidParentheses.isValidParentheses("(]")); } } diff --git a/src/test/java/com/thealgorithms/strings/WordLadderTest.java b/src/test/java/com/thealgorithms/strings/WordLadderTest.java index d933ebeddc53..221953411da7 100644 --- a/src/test/java/com/thealgorithms/strings/WordLadderTest.java +++ b/src/test/java/com/thealgorithms/strings/WordLadderTest.java @@ -1,5 +1,6 @@ package com.thealgorithms.strings; +import static java.util.Collections.emptyList; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.Arrays; @@ -41,6 +42,21 @@ public void testWordLadder2() { assertEquals(WordLadder.ladderLength("hit", "cog", wordList2), 0); } + /** + * Test 3: + * Input: beginWord = "hit", endWord = "cog", wordList = + * [] + * Output: 0 + * Explanation: The wordList is empty (corner case), + * therefore there is no valid transformation sequence. + */ + @Test + public void testWordLadder3() { + + List wordList3 = emptyList(); + assertEquals(WordLadder.ladderLength("hit", "cog", wordList3), 0); + } + @ParameterizedTest @CsvSource({"'a', 'c', 'b,c', 2", "'a', 'c', 'a', 0", "'a', 'a', 'a', 0", "'ab', 'cd', 'ad,bd,cd', 3", "'a', 'd', 'b,c,d', 2", "'a', 'd', 'b,c,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,d', 2"}) void testLadderLength(String beginWord, String endWord, String wordListStr, int expectedLength) { diff --git a/src/test/java/com/thealgorithms/strings/zigZagPattern/ZigZagPatternTest.java b/src/test/java/com/thealgorithms/strings/zigZagPattern/ZigZagPatternTest.java index 518bfab80f08..2cbbfe3d2dd8 100644 --- a/src/test/java/com/thealgorithms/strings/zigZagPattern/ZigZagPatternTest.java +++ b/src/test/java/com/thealgorithms/strings/zigZagPattern/ZigZagPatternTest.java @@ -6,10 +6,14 @@ public class ZigZagPatternTest { @Test - public void palindrome() { + public void testZigZagPattern() { String input1 = "HelloWorldFromJava"; String input2 = "javaIsAProgrammingLanguage"; Assertions.assertEquals(ZigZagPattern.encode(input1, 4), "HooeWrrmalolFJvlda"); Assertions.assertEquals(ZigZagPattern.encode(input2, 4), "jAaLgasPrmgaaevIrgmnnuaoig"); + // Edge cases + Assertions.assertEquals("ABC", ZigZagPattern.encode("ABC", 1)); // Single row + Assertions.assertEquals("A", ZigZagPattern.encode("A", 2)); // numRows > length of string + Assertions.assertEquals("", ZigZagPattern.encode("", 3)); // Empty string } } diff --git a/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java b/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java new file mode 100644 index 000000000000..29189290e1d4 --- /dev/null +++ b/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java @@ -0,0 +1,69 @@ +package com.thealgorithms.tree; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class HeavyLightDecompositionTest { + + private HeavyLightDecomposition hld; + private final int[] values = {0, 10, 20, 30, 40, 50}; + + /** + * Initializes the test environment with a predefined tree structure and values. + */ + @BeforeEach + void setUp() { + hld = new HeavyLightDecomposition(5); + hld.addEdge(1, 2); + hld.addEdge(1, 3); + hld.addEdge(2, 4); + hld.addEdge(2, 5); + hld.initialize(1, values); + } + + /** + * Verifies that the tree initializes successfully without errors. + */ + @Test + void testBasicTreeInitialization() { + assertTrue(true, "Basic tree structure initialized successfully"); + } + + /** + * Tests the maximum value query in the path between nodes. + */ + @Test + void testQueryMaxInPath() { + assertEquals(50, hld.queryMaxInPath(4, 5), "Max value in path (4,5) should be 50"); + assertEquals(30, hld.queryMaxInPath(3, 2), "Max value in path (3,2) should be 30"); + } + + /** + * Tests updating a node's value and ensuring it is reflected in queries. + */ + @Test + void testUpdateNodeValue() { + hld.updateSegmentTree(1, 0, hld.getPositionIndex() - 1, hld.getPosition(4), 100); + assertEquals(100, hld.queryMaxInPath(4, 5), "Updated value should be reflected in query"); + } + + /** + * Tests the maximum value query in a skewed tree structure. + */ + @Test + void testSkewedTreeMaxQuery() { + assertEquals(40, hld.queryMaxInPath(1, 4), "Max value in skewed tree (1,4) should be 40"); + } + + /** + * Ensures query handles cases where u is a deeper node correctly. + */ + @Test + void testDepthSwapInPathQuery() { + assertEquals(50, hld.queryMaxInPath(5, 2), "Query should handle depth swap correctly"); + assertEquals(40, hld.queryMaxInPath(4, 1), "Query should handle swapped nodes correctly and return max value"); + } +}