diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2e5622f7b51d..1b91763b2d53 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,14 +5,20 @@ updates: directory: "/" schedule: interval: "weekly" + cooldown: + default-days: 7 - package-ecosystem: "github-actions" directory: "/.github/workflows/" schedule: interval: "daily" + cooldown: + default-days: 7 - package-ecosystem: "maven" directory: "/" schedule: interval: "daily" + cooldown: + default-days: 7 ... diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c2c1ef828b7..03ca693a5af0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,9 +8,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@v5.6.0 with: java-version: 21 distribution: 'temurin' @@ -20,7 +20,7 @@ jobs: if: >- github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository - uses: codecov/codecov-action@v6 + uses: codecov/codecov-action@v7 with: fail_ci_if_error: true - name: Upload coverage to codecov (with token) @@ -28,7 +28,7 @@ jobs: github.repository == 'TheAlgorithms/Java' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) - uses: codecov/codecov-action@v6 + uses: codecov/codecov-action@v7 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true diff --git a/.github/workflows/clang-format-lint.yml b/.github/workflows/clang-format-lint.yml index dc0c9754ed1b..622679f73842 100644 --- a/.github/workflows/clang-format-lint.yml +++ b/.github/workflows/clang-format-lint.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: DoozyX/clang-format-lint-action@v0.20 with: source: './src' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 152d0d766fd2..59cfcb3e43f2 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -21,16 +21,16 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@v5.6.0 with: java-version: 21 distribution: 'temurin' - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@v4.37.1 with: languages: 'java-kotlin' @@ -38,7 +38,7 @@ jobs: run: mvn --batch-mode --update-snapshots verify - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@v4.37.1 with: category: "/language:java-kotlin" @@ -52,15 +52,15 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@v4.37.1 with: languages: 'actions' - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@v4.37.1 with: category: "/language:actions" ... diff --git a/.github/workflows/infer.yml b/.github/workflows/infer.yml index 9d4dcf63000b..cc15da8d0b00 100644 --- a/.github/workflows/infer.yml +++ b/.github/workflows/infer.yml @@ -15,42 +15,16 @@ jobs: run_infer: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@v5.6.0 with: java-version: 21 distribution: 'temurin' - - name: Set up OCaml - uses: ocaml/setup-ocaml@v3 - with: - ocaml-compiler: 5 - - - name: Get current year/weak - run: echo "year_week=$(date +'%Y_%U')" >> $GITHUB_ENV - - - name: Cache infer build - id: cache-infer - uses: actions/cache@v5 - with: - path: infer - key: ${{ runner.os }}-infer-${{ env.year_week }} - - - name: Build infer - if: steps.cache-infer.outputs.cache-hit != 'true' - run: | - cd .. - git clone https://github.com/facebook/infer.git - cd infer - git checkout 02c2c43b71e4c5110c0be841e66153942fda06c9 - ./build-infer.sh java - cp -r infer ../Java - - - name: Add infer to PATH - run: | - echo "infer/bin" >> $GITHUB_PATH + - name: Set up inferAdd commentMore actions + uses: srz-zumix/setup-infer@v1 - name: Display infer version run: | @@ -60,5 +34,5 @@ jobs: - name: Run infer run: | mvn clean - infer --fail-on-issue --print-logs --no-progress-bar -- mvn test + infer --java-version 21 --fail-on-issue --print-logs --no-progress-bar -- mvn test ... diff --git a/.github/workflows/project_structure.yml b/.github/workflows/project_structure.yml index 5aadc6353791..3f27ad13a9cb 100644 --- a/.github/workflows/project_structure.yml +++ b/.github/workflows/project_structure.yml @@ -15,8 +15,8 @@ jobs: check_structure: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: actions/setup-python@v6 + - uses: actions/checkout@v7 + - uses: actions/setup-python@v6.3.0 with: python-version: '3.13' diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index bb613daf8f1d..c94d2040aac4 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -11,7 +11,7 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/stale@v10 + - uses: actions/stale@v10.4.0 with: stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contribution!' close-issue-message: 'Please reopen this issue once you have made the required changes. If you need help, feel free to ask in our [Discord](https://the-algorithms.com/discord) server or ping one of the maintainers here. Thank you for your contribution!' diff --git a/.github/workflows/update-directorymd.yml b/.github/workflows/update-directorymd.yml index 1cfee6e36e4e..3977bfda86bf 100644 --- a/.github/workflows/update-directorymd.yml +++ b/.github/workflows/update-directorymd.yml @@ -1,4 +1,4 @@ -name: Generate Directory Markdown +name: Generate Directory Markdown on: push: @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: persist-credentials: false diff --git a/.inferconfig b/.inferconfig index 239172177b38..cf26212feac5 100644 --- a/.inferconfig +++ b/.inferconfig @@ -21,6 +21,7 @@ "src/test/java/com/thealgorithms/datastructures/lists/SkipListTest.java", "src/test/java/com/thealgorithms/datastructures/trees/KDTreeTest.java", "src/test/java/com/thealgorithms/datastructures/trees/LazySegmentTreeTest.java", + "src/test/java/com/thealgorithms/dynamicprogramming/DamerauLevenshteinDistanceTest.java", "src/test/java/com/thealgorithms/others/HuffmanTest.java", "src/test/java/com/thealgorithms/searches/QuickSelectTest.java", "src/test/java/com/thealgorithms/stacks/PostfixToInfixTest.java", diff --git a/pmd-exclude.properties b/pmd-exclude.properties index 64562c524728..26653d9dc17b 100644 --- a/pmd-exclude.properties +++ b/pmd-exclude.properties @@ -86,7 +86,7 @@ com.thealgorithms.others.MosAlgorithm=UselessMainMethod com.thealgorithms.others.PageRank=UselessMainMethod,UselessParentheses com.thealgorithms.others.PerlinNoise=UselessMainMethod,UselessParentheses com.thealgorithms.others.QueueUsingTwoStacks=UselessParentheses -com.thealgorithms.others.Trieac=UselessMainMethod,UselessParentheses +com.thealgorithms.datastructures.trees.TrieAutocomplete=UselessMainMethod,UselessParentheses com.thealgorithms.others.Verhoeff=UnnecessaryFullyQualifiedName,UselessMainMethod com.thealgorithms.recursion.DiceThrower=UselessMainMethod com.thealgorithms.searches.HowManyTimesRotated=UselessMainMethod diff --git a/pom.xml b/pom.xml index e0a3486b23bb..80fb3a6f8b2c 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ org.junit junit-bom - 6.0.3 + 6.1.2 pom import @@ -61,7 +61,7 @@ maven-surefire-plugin - 3.5.5 + 3.5.6 @@ -82,7 +82,7 @@ org.jacoco jacoco-maven-plugin - 0.8.14 + 0.8.15 @@ -112,14 +112,14 @@ com.puppycrawl.tools checkstyle - 13.4.2 + 13.8.0 com.github.spotbugs spotbugs-maven-plugin - 4.9.8.3 + 4.10.3.0 spotbugs-exclude.xml true diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index 8c42802520e3..1c34e47fb651 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -59,6 +59,12 @@ + + + + + + diff --git a/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java b/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java index 0dd23e937953..4a9e954bd202 100644 --- a/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java +++ b/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java @@ -3,16 +3,19 @@ /** * Exponential Moving Average (EMA) Filter for smoothing audio signals. * - *

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

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

Based on the definition from + *

+ * Based on the definition from * Wikipedia link. */ public class EMAFilter { private final double alpha; private double emaValue; + /** * Constructs an EMA filter with a given smoothing factor. * @@ -26,14 +29,17 @@ public EMAFilter(double alpha) { this.alpha = alpha; this.emaValue = 0.0; } + /** * Applies the EMA filter to an audio signal array. + * EMA formula: + * EMA = alpha * currentSample + (1 - alpha) * previousEMA * * @param audioSignal Array of audio samples to process * @return Array of processed (smoothed) samples */ public double[] apply(double[] audioSignal) { - if (audioSignal.length == 0) { + if (audioSignal == null || audioSignal.length == 0) { return new double[0]; } double[] emaSignal = new double[audioSignal.length]; diff --git a/src/main/java/com/thealgorithms/backtracking/NQueens.java b/src/main/java/com/thealgorithms/backtracking/NQueens.java index 1a8e453e34cb..404f677738a0 100644 --- a/src/main/java/com/thealgorithms/backtracking/NQueens.java +++ b/src/main/java/com/thealgorithms/backtracking/NQueens.java @@ -1,7 +1,9 @@ package com.thealgorithms.backtracking; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Problem statement: Given a N x N chess board. Return all arrangements in @@ -32,7 +34,22 @@ * queen is not placed safely. If there is no such way then return an empty list * as solution */ + +/* + * Time Complexity: O(N!) + * space Complexity: O(N) + */ public final class NQueens { + + // Store occupied rows for constant time safety check + private static final Set OCROWS = new HashSet<>(); + + // Store occupied main diagonals (row - column) + private static final Set OCDIAG = new HashSet<>(); + + // Store occupied anti-diagonals (row + columns) + private static final Set OCANTIDIAG = new HashSet<>(); + private NQueens() { } @@ -43,10 +60,10 @@ public static List> getNQueensArrangements(int queens) { } public static void placeQueens(final int queens) { - List> arrangements = new ArrayList>(); + List> arrangements = new ArrayList<>(); getSolution(queens, arrangements, new int[queens], 0); if (arrangements.isEmpty()) { - System.out.println("There is no way to place " + queens + " queens on board of size " + queens + "x" + queens); + System.out.println(" no way to place " + queens + " queens on board of size " + queens + "x" + queens); } else { System.out.println("Arrangement for placing " + queens + " queens"); } @@ -59,15 +76,15 @@ public static void placeQueens(final int queens) { /** * This is backtracking function which tries to place queen recursively * - * @param boardSize: size of chess board - * @param solutions: this holds all possible arrangements - * @param columns: columns[i] = rowId where queen is placed in ith column. + * @param boardSize: size of chess board + * @param solutions: this holds all possible arrangements + * @param columns: columns[i] = rowId where queen is placed in ith column. * @param columnIndex: This is the column in which queen is being placed */ private static void getSolution(int boardSize, List> solutions, int[] columns, int columnIndex) { if (columnIndex == boardSize) { // this means that all queens have been placed - List sol = new ArrayList(); + List sol = new ArrayList<>(); for (int i = 0; i < boardSize; i++) { StringBuilder sb = new StringBuilder(); for (int j = 0; j < boardSize; j++) { @@ -82,30 +99,29 @@ private static void getSolution(int boardSize, List> solutions, int // This loop tries to place queen in a row one by one for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { columns[columnIndex] = rowIndex; - if (isPlacedCorrectly(columns, rowIndex, columnIndex)) { - // If queen is placed successfully at rowIndex in column=columnIndex then try - // placing queen in next column - getSolution(boardSize, solutions, columns, columnIndex + 1); - } - } - } - /** - * This function checks if queen can be placed at row = rowIndex in column = - * columnIndex safely - * - * @param columns: columns[i] = rowId where queen is placed in ith column. - * @param rowIndex: row in which queen has to be placed - * @param columnIndex: column in which queen is being placed - * @return true: if queen can be placed safely false: otherwise - */ - private static boolean isPlacedCorrectly(int[] columns, int rowIndex, int columnIndex) { - for (int i = 0; i < columnIndex; i++) { - int diff = Math.abs(columns[i] - rowIndex); - if (diff == 0 || columnIndex - i == diff) { - return false; + // Skip current position if row or diagonal is already occupied + boolean isROp = OCROWS.contains(rowIndex); + + boolean isDOp = OCDIAG.contains(rowIndex - columnIndex) || OCANTIDIAG.contains(rowIndex + columnIndex); + + if (isROp || isDOp) { + continue; } + + // Mark current row and diagonal as occupied + OCROWS.add(rowIndex); + OCDIAG.add(rowIndex - columnIndex); + OCANTIDIAG.add(rowIndex + columnIndex); + + // Move to the next column after placing current queen + getSolution(boardSize, solutions, columns, columnIndex + 1); + + // Backtrack by removing current queen + + OCROWS.remove(rowIndex); + OCDIAG.remove(rowIndex - columnIndex); + OCANTIDIAG.remove(rowIndex + columnIndex); } - return true; } } diff --git a/src/main/java/com/thealgorithms/backtracking/RatInAMaze.java b/src/main/java/com/thealgorithms/backtracking/RatInAMaze.java new file mode 100644 index 000000000000..183b4bbd97f8 --- /dev/null +++ b/src/main/java/com/thealgorithms/backtracking/RatInAMaze.java @@ -0,0 +1,119 @@ +package com.thealgorithms.backtracking; + +import java.util.ArrayList; +import java.util.List; + +/** + * Rat in a Maze Problem using Backtracking. + * + *

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

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

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

Example: + *

+ *   maze = { {1, 0, 0, 0},
+ *            {1, 1, 0, 1},
+ *            {0, 1, 0, 0},
+ *            {0, 1, 1, 1} }
+ *   Output: ["DDRDRR", "DRDDRR"]  (two valid paths)
+ * 
+ * + * @see Maze solving algorithm + * @author the-Sunny-Sharma (GitHub) + */ +public final class RatInAMaze { + + private RatInAMaze() { + } + + /** + * Finds all paths from the top-left to the bottom-right of the given maze. + * + * @param maze an {@code n x n} binary matrix where {@code 1} = open, {@code 0} = blocked + * @return a sorted list of all valid path strings using directions D, L, R, U; + * an empty list if no path exists + * @throws IllegalArgumentException if the maze is null, empty, or not square + */ + public static List findPaths(final int[][] maze) { + if (maze == null || maze.length == 0) { + throw new IllegalArgumentException("Maze must not be null or empty."); + } + int n = maze.length; + for (int[] row : maze) { + if (row.length != n) { + throw new IllegalArgumentException("Maze must be a square (n x n) matrix."); + } + } + List results = new ArrayList<>(); + if (maze[0][0] == 0 || maze[n - 1][n - 1] == 0) { + return results; + } + boolean[][] visited = new boolean[n][n]; + solve(maze, 0, 0, n, "", visited, results); + return results; + } + + /** + * Recursive backtracking helper that explores all four directions. + * + * @param maze the binary maze + * @param row current row position + * @param col current column position + * @param n maze dimension + * @param path path string built so far + * @param visited tracks visited cells for the current path + * @param results accumulates complete paths + */ + private static void solve(final int[][] maze, final int row, final int col, final int n, final String path, final boolean[][] visited, final List results) { + // Base case: reached destination + if (row == n - 1 && col == n - 1) { + results.add(path); + return; + } + + // Mark current cell as visited + visited[row][col] = true; + + // Explore in alphabetical order: Down, Left, Right, Up + // Down + if (isSafe(maze, row + 1, col, n, visited)) { + solve(maze, row + 1, col, n, path + 'D', visited, results); + } + // Left + if (isSafe(maze, row, col - 1, n, visited)) { + solve(maze, row, col - 1, n, path + 'L', visited, results); + } + // Right + if (isSafe(maze, row, col + 1, n, visited)) { + solve(maze, row, col + 1, n, path + 'R', visited, results); + } + // Up + if (isSafe(maze, row - 1, col, n, visited)) { + solve(maze, row - 1, col, n, path + 'U', visited, results); + } + + // Backtrack: unmark current cell + visited[row][col] = false; + } + + /** + * Checks whether moving to {@code (row, col)} is valid. + * + * @param maze the binary maze + * @param row target row + * @param col target column + * @param n maze dimension + * @param visited tracks visited cells for the current path + * @return {@code true} if the cell is within bounds, open, and not yet visited + */ + private static boolean isSafe(final int[][] maze, final int row, final int col, final int n, final boolean[][] visited) { + return row >= 0 && row < n && col >= 0 && col < n && maze[row][col] == 1 && !visited[row][col]; + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java b/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java index 0d6fd140c720..5038d44079ec 100644 --- a/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java +++ b/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java @@ -9,6 +9,12 @@ *

* * @author Hardvan + * @see com.thealgorithms.strings.Palindrome + * @see com.thealgorithms.stacks.PalindromeWithStack + * @see com.thealgorithms.maths.LowestBasePalindrome + * @see com.thealgorithms.datastructures.lists.PalindromeSinglyLinkedList + * @see com.thealgorithms.maths.PalindromePrime + * @see com.thealgorithms.maths.PalindromeNumber */ public final class BinaryPalindromeCheck { private BinaryPalindromeCheck() { diff --git a/src/main/java/com/thealgorithms/ciphers/Caesar.java b/src/main/java/com/thealgorithms/ciphers/Caesar.java index 23535bc2b5d2..7a5e70e6eb78 100644 --- a/src/main/java/com/thealgorithms/ciphers/Caesar.java +++ b/src/main/java/com/thealgorithms/ciphers/Caesar.java @@ -9,8 +9,8 @@ * @author khalil2535 */ public class Caesar { - private static char normalizeShift(final int shift) { - return (char) (shift % 26); + private static int normalizeShift(final int shift) { + return ((shift % 26) + 26) % 26; } /** @@ -22,21 +22,18 @@ private static char normalizeShift(final int shift) { public String encode(String message, int shift) { StringBuilder encoded = new StringBuilder(); - final char shiftChar = normalizeShift(shift); + final int shiftChar = normalizeShift(shift); final int length = message.length(); for (int i = 0; i < length; i++) { - // int current = message.charAt(i); //using char to shift characters because - // ascii - // is in-order latin alphabet - char current = message.charAt(i); // Java law : char + int = char + final char current = message.charAt(i); if (isCapitalLatinLetter(current)) { - current += shiftChar; - encoded.append((char) (current > 'Z' ? current - 26 : current)); // 26 = number of latin letters + final int shifted = current + shiftChar; + encoded.append((char) (shifted > 'Z' ? shifted - 26 : shifted)); // 26 = number of latin letters } else if (isSmallLatinLetter(current)) { - current += shiftChar; - encoded.append((char) (current > 'z' ? current - 26 : current)); // 26 = number of latin letters + final int shifted = current + shiftChar; + encoded.append((char) (shifted > 'z' ? shifted - 26 : shifted)); // 26 = number of latin letters } else { encoded.append(current); } @@ -53,17 +50,17 @@ public String encode(String message, int shift) { public String decode(String encryptedMessage, int shift) { StringBuilder decoded = new StringBuilder(); - final char shiftChar = normalizeShift(shift); + final int shiftChar = normalizeShift(shift); final int length = encryptedMessage.length(); for (int i = 0; i < length; i++) { - char current = encryptedMessage.charAt(i); + final char current = encryptedMessage.charAt(i); if (isCapitalLatinLetter(current)) { - current -= shiftChar; - decoded.append((char) (current < 'A' ? current + 26 : current)); // 26 = number of latin letters + final int shifted = current - shiftChar; + decoded.append((char) (shifted < 'A' ? shifted + 26 : shifted)); // 26 = number of latin letters } else if (isSmallLatinLetter(current)) { - current -= shiftChar; - decoded.append((char) (current < 'a' ? current + 26 : current)); // 26 = number of latin letters + final int shifted = current - shiftChar; + decoded.append((char) (shifted < 'a' ? shifted + 26 : shifted)); // 26 = number of latin letters } else { decoded.append(current); } diff --git a/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java b/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java index 3d31cb3e7f6c..314e7fba38a3 100644 --- a/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java +++ b/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java @@ -1,10 +1,4 @@ -/** - * [Brief description of what the algorithm does] - *

- * Time Complexity: O(n) [or appropriate complexity] - * Space Complexity: O(n) - * @author Reshma Kakkirala - */ + package com.thealgorithms.conversions; import java.util.Arrays; diff --git a/src/main/java/com/thealgorithms/conversions/Base64.java b/src/main/java/com/thealgorithms/conversions/Base64.java index 5219c4ba7f4e..fb4411b399a3 100644 --- a/src/main/java/com/thealgorithms/conversions/Base64.java +++ b/src/main/java/com/thealgorithms/conversions/Base64.java @@ -119,8 +119,15 @@ public static byte[] decode(String input) { // Validate padding: '=' can only appear at the end (last 1 or 2 chars) int firstPadding = input.indexOf('='); - if (firstPadding != -1 && firstPadding < input.length() - 2) { - throw new IllegalArgumentException("Padding '=' can only appear at the end (last 1 or 2 characters)"); + if (firstPadding != -1) { + if (firstPadding < input.length() - 2) { + throw new IllegalArgumentException("Padding '=' can only appear at the end (last 1 or 2 characters)"); + } + for (int i = firstPadding; i < input.length(); i++) { + if (input.charAt(i) != '=') { + throw new IllegalArgumentException("A padding '=' must not be followed by a non-padding character"); + } + } } List result = new ArrayList<>(); diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithm.java index 70699a9461f7..1b5f765843be 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithm.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithm.java @@ -1,6 +1,7 @@ package com.thealgorithms.datastructures.graphs; import java.util.Arrays; +import java.util.PriorityQueue; /** * Dijkstra's algorithm for finding the shortest path from a single source vertex to all other vertices in a graph. @@ -18,6 +19,21 @@ public DijkstraAlgorithm(int vertexCount) { this.vertexCount = vertexCount; } + private static class Node implements Comparable { + int id; + int distance; + + Node(int id, int distance) { + this.id = id; + this.distance = distance; + } + + @Override + public int compareTo(Node other) { + return Integer.compare(this.distance, other.distance); + } + } + /** * Executes Dijkstra's algorithm on the provided graph to find the shortest paths from the source vertex to all other vertices. * @@ -36,18 +52,25 @@ public int[] run(int[][] graph, int source) { int[] distances = new int[vertexCount]; boolean[] processed = new boolean[vertexCount]; + PriorityQueue unprocessed = new PriorityQueue<>(); Arrays.fill(distances, Integer.MAX_VALUE); - Arrays.fill(processed, false); distances[source] = 0; + unprocessed.add(new Node(source, 0)); + + while (!unprocessed.isEmpty()) { + Node current = unprocessed.poll(); + int u = current.id; - for (int count = 0; count < vertexCount - 1; count++) { - int u = getMinDistanceVertex(distances, processed); + if (processed[u]) { + continue; + } processed[u] = true; for (int v = 0; v < vertexCount; v++) { if (!processed[v] && graph[u][v] != 0 && distances[u] != Integer.MAX_VALUE && distances[u] + graph[u][v] < distances[v]) { distances[v] = distances[u] + graph[u][v]; + unprocessed.add(new Node(v, distances[v])); } } } @@ -56,27 +79,6 @@ public int[] run(int[][] graph, int source) { return distances; } - /** - * Finds the vertex with the minimum distance value from the set of vertices that have not yet been processed. - * - * @param distances The array of current shortest distances from the source vertex. - * @param processed The array indicating whether each vertex has been processed. - * @return The index of the vertex with the minimum distance value. - */ - private int getMinDistanceVertex(int[] distances, boolean[] processed) { - int min = Integer.MAX_VALUE; - int minIndex = -1; - - for (int v = 0; v < vertexCount; v++) { - if (!processed[v] && distances[v] <= min) { - min = distances[v]; - minIndex = v; - } - } - - return minIndex; - } - /** * Prints the shortest distances from the source vertex to all other vertices. * diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java deleted file mode 100644 index a686b808a970..000000000000 --- a/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.thealgorithms.datastructures.graphs; - -import java.util.Arrays; -import java.util.Set; -import java.util.TreeSet; -import org.apache.commons.lang3.tuple.Pair; - -/** - * Dijkstra's algorithm for finding the shortest path from a single source vertex to all other vertices in a graph. - */ -public class DijkstraOptimizedAlgorithm { - - private final int vertexCount; - - /** - * Constructs a Dijkstra object with the given number of vertices. - * - * @param vertexCount The number of vertices in the graph. - */ - public DijkstraOptimizedAlgorithm(int vertexCount) { - this.vertexCount = vertexCount; - } - - /** - * Executes Dijkstra's algorithm on the provided graph to find the shortest paths from the source vertex to all other vertices. - * - * The graph is represented as an adjacency matrix where {@code graph[i][j]} represents the weight of the edge from vertex {@code i} - * to vertex {@code j}. A value of 0 indicates no edge exists between the vertices. - * - * @param graph The graph represented as an adjacency matrix. - * @param source The source vertex. - * @return An array where the value at each index {@code i} represents the shortest distance from the source vertex to vertex {@code i}. - * @throws IllegalArgumentException if the source vertex is out of range. - */ - public int[] run(int[][] graph, int source) { - if (source < 0 || source >= vertexCount) { - throw new IllegalArgumentException("Incorrect source"); - } - - int[] distances = new int[vertexCount]; - boolean[] processed = new boolean[vertexCount]; - Set> unprocessed = new TreeSet<>(); - - Arrays.fill(distances, Integer.MAX_VALUE); - Arrays.fill(processed, false); - distances[source] = 0; - unprocessed.add(Pair.of(0, source)); - - while (!unprocessed.isEmpty()) { - Pair distanceAndU = unprocessed.iterator().next(); - unprocessed.remove(distanceAndU); - int u = distanceAndU.getRight(); - processed[u] = true; - - for (int v = 0; v < vertexCount; v++) { - if (!processed[v] && graph[u][v] != 0 && distances[u] != Integer.MAX_VALUE && distances[u] + graph[u][v] < distances[v]) { - unprocessed.remove(Pair.of(distances[v], v)); - distances[v] = distances[u] + graph[u][v]; - unprocessed.add(Pair.of(distances[v], v)); - } - } - } - - return distances; - } -} diff --git a/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedList.java similarity index 84% rename from src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java rename to src/main/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedList.java index c81476eaec32..7bb16921b9ef 100644 --- a/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedList.java @@ -1,4 +1,4 @@ -package com.thealgorithms.misc; +package com.thealgorithms.datastructures.lists; import java.util.Stack; @@ -9,6 +9,13 @@ * * See more: * https://www.geeksforgeeks.org/function-to-check-if-a-singly-linked-list-is-palindrome/ + * + * @see com.thealgorithms.strings.Palindrome + * @see com.thealgorithms.stacks.PalindromeWithStack + * @see com.thealgorithms.bitmanipulation.BinaryPalindromeCheck + * @see com.thealgorithms.maths.LowestBasePalindrome + * @see com.thealgorithms.maths.PalindromePrime + * @see com.thealgorithms.maths.PalindromeNumber */ @SuppressWarnings("rawtypes") public final class PalindromeSinglyLinkedList { diff --git a/src/main/java/com/thealgorithms/datastructures/queues/ThreadSafeQueue.java b/src/main/java/com/thealgorithms/datastructures/queues/ThreadSafeQueue.java new file mode 100644 index 000000000000..a943b0028974 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/queues/ThreadSafeQueue.java @@ -0,0 +1,186 @@ +package com.thealgorithms.datastructures.queues; + +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @brief Thread-safe bounded queue implementation using ReentrantLock and Condition variables + * @details A blocking queue that supports multiple producers and consumers. + * Uses a circular buffer internally with lock-based synchronization to ensure + * thread safety. Producers block when the queue is full, and consumers block + * when the queue is empty. + * @see Producer-Consumer Problem + */ +public class ThreadSafeQueue { + + private final Object[] buffer; + private final int capacity; + private int head; + private int tail; + private int count; + private final ReentrantLock lock; + private final Condition notFull; + private final Condition notEmpty; + + /** + * @brief Constructs a ThreadSafeQueue with the specified capacity + * @param capacity the maximum number of elements the queue can hold + * @throws IllegalArgumentException if capacity is less than or equal to zero + */ + public ThreadSafeQueue(int capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Capacity must be greater than zero."); + } + this.capacity = capacity; + this.buffer = new Object[capacity]; + this.head = 0; + this.tail = 0; + this.count = 0; + this.lock = new ReentrantLock(); + this.notFull = lock.newCondition(); + this.notEmpty = lock.newCondition(); + } + + /** + * @brief Adds an element to the tail of the queue, blocking if full + * @param item the element to add + * @throws InterruptedException if the thread is interrupted while waiting + * @throws IllegalArgumentException if the item is null + */ + public void enqueue(T item) throws InterruptedException { + if (item == null) { + throw new IllegalArgumentException("Cannot enqueue null item."); + } + + lock.lock(); + try { + while (count == capacity) { + notFull.await(); + } + buffer[tail] = item; + tail = (tail + 1) % capacity; + count++; + notEmpty.signalAll(); + } finally { + lock.unlock(); + } + } + + /** + * @brief Removes and returns the element at the head of the queue, blocking if empty + * @return the element at the head of the queue + * @throws InterruptedException if the thread is interrupted while waiting + */ + @SuppressWarnings("unchecked") + public T dequeue() throws InterruptedException { + lock.lock(); + try { + while (count == 0) { + notEmpty.await(); + } + T item = (T) buffer[head]; + buffer[head] = null; + head = (head + 1) % capacity; + count--; + notFull.signalAll(); + return item; + } finally { + lock.unlock(); + } + } + + /** + * @brief Adds an element to the tail of the queue without blocking + * @param item the element to add + * @return true if the element was added, false if the queue was full + * @throws IllegalArgumentException if the item is null + */ + public boolean offer(T item) { + if (item == null) { + throw new IllegalArgumentException("Cannot enqueue null item."); + } + + lock.lock(); + try { + if (count == capacity) { + return false; + } + buffer[tail] = item; + tail = (tail + 1) % capacity; + count++; + notEmpty.signalAll(); + return true; + } finally { + lock.unlock(); + } + } + + /** + * @brief Removes and returns the element at the head without blocking + * @return the element at the head, or null if the queue is empty + */ + @SuppressWarnings("unchecked") + public T poll() { + lock.lock(); + try { + if (count == 0) { + return null; + } + T item = (T) buffer[head]; + buffer[head] = null; + head = (head + 1) % capacity; + count--; + notFull.signalAll(); + return item; + } finally { + lock.unlock(); + } + } + + /** + * @brief Returns the number of elements in the queue + * @return the current size of the queue + */ + public int size() { + lock.lock(); + try { + return count; + } finally { + lock.unlock(); + } + } + + /** + * @brief Checks if the queue is empty + * @return true if the queue contains no elements + */ + public boolean isEmpty() { + lock.lock(); + try { + return count == 0; + } finally { + lock.unlock(); + } + } + + /** + * @brief Checks if the queue is full + * @return true if the queue has reached its capacity + */ + public boolean isFull() { + lock.lock(); + try { + return count == capacity; + } finally { + lock.unlock(); + } + } + + /** + * @brief Returns the maximum capacity of the queue + * @return the capacity + */ + public int capacity() { + return capacity; + } +} diff --git a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java b/src/main/java/com/thealgorithms/datastructures/trees/HeavyLightDecomposition.java similarity index 99% rename from src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java rename to src/main/java/com/thealgorithms/datastructures/trees/HeavyLightDecomposition.java index 236a23205180..ed67f9ae3394 100644 --- a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java +++ b/src/main/java/com/thealgorithms/datastructures/trees/HeavyLightDecomposition.java @@ -1,4 +1,4 @@ -package com.thealgorithms.tree; +package com.thealgorithms.datastructures.trees; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/thealgorithms/others/Implementing_auto_completing_features_using_trie.java b/src/main/java/com/thealgorithms/datastructures/trees/TrieAutocomplete.java similarity index 98% rename from src/main/java/com/thealgorithms/others/Implementing_auto_completing_features_using_trie.java rename to src/main/java/com/thealgorithms/datastructures/trees/TrieAutocomplete.java index 7a1a7aadd805..624e3d65bfc1 100644 --- a/src/main/java/com/thealgorithms/others/Implementing_auto_completing_features_using_trie.java +++ b/src/main/java/com/thealgorithms/datastructures/trees/TrieAutocomplete.java @@ -1,8 +1,8 @@ -package com.thealgorithms.others; +package com.thealgorithms.datastructures.trees; // Java Program to implement Auto-Complete // Feature using Trie -class Trieac { +class TrieAutocomplete { // Alphabet size (# of symbols) public static final int ALPHABET_SIZE = 26; diff --git a/src/main/java/com/thealgorithms/datastructures/trees/WaveletTree.java b/src/main/java/com/thealgorithms/datastructures/trees/WaveletTree.java new file mode 100644 index 000000000000..6feaa6f35048 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/trees/WaveletTree.java @@ -0,0 +1,235 @@ +package com.thealgorithms.datastructures.trees; + +import java.util.ArrayList; +import java.util.List; + +/** + * A Wavelet Tree is a highly efficient data structure used to store sequences + * and answer queries like rank, select, and quantile in O(log(max_val - min_val)) time. + * This structure is particularly useful in competitive programming and text compression. + */ +public class WaveletTree { + + private class Node { + int low; + int high; + Node left; + Node right; + List leftCount; // Prefix sums of elements going to the left child + + /** + * Recursively constructs the tree nodes by partitioning the array. + * + * @param arr the subarray for the current node + * @param low the minimum possible value in the current node + * @param high the maximum possible value in the current node + */ + Node(int[] arr, int low, int high) { + this.low = low; + this.high = high; + + if (low == high) { + return; + } + + int mid = low + (high - low) / 2; + leftCount = new ArrayList<>(arr.length + 1); + leftCount.add(0); + + List leftArr = new ArrayList<>(); + List rightArr = new ArrayList<>(); + + for (int x : arr) { + if (x <= mid) { + leftArr.add(x); + leftCount.add(leftCount.get(leftCount.size() - 1) + 1); + } else { + rightArr.add(x); + leftCount.add(leftCount.get(leftCount.size() - 1)); + } + } + + if (!leftArr.isEmpty()) { + this.left = new Node(leftArr.stream().mapToInt(i -> i).toArray(), low, mid); + } + if (!rightArr.isEmpty()) { + this.right = new Node(rightArr.stream().mapToInt(i -> i).toArray(), mid + 1, high); + } + } + } + + private Node root; + private final int n; + + /** + * Constructs a Wavelet Tree from the given array. + * The min and max values are determined dynamically from the array. + * + * @param arr the input array + */ + public WaveletTree(int[] arr) { + if (arr == null || arr.length == 0) { + this.n = 0; + return; + } + this.n = arr.length; + int min = arr[0]; + int max = arr[0]; + for (int x : arr) { + if (x < min) { + min = x; + } + if (x > max) { + max = x; + } + } + root = new Node(arr, min, max); + } + + /** + * Constructs a Wavelet Tree from the given array with specific min and max values. + * + * @param arr the input array + * @param minValue the minimum possible value + * @param maxValue the maximum possible value + */ + public WaveletTree(int[] arr, int minValue, int maxValue) { + if (arr == null || arr.length == 0) { + this.n = 0; + return; + } + this.n = arr.length; + root = new Node(arr, minValue, maxValue); + } + + /** + * How many times does the number x appear in the array from index 0 to i (inclusive)? + * + * @param x the number to search for + * @param i the end index (0-based, inclusive) + * @return the number of occurrences of x in arr[0...i] + */ + public int rank(int x, int i) { + if (root == null || x < root.low || x > root.high || i < 0) { + return 0; + } + // If i is out of bounds, cap it at n - 1 + int endIdx = Math.min(i, n - 1); + return rank(root, x, endIdx + 1); + } + + private int rank(Node node, int x, int count) { + if (node == null || count == 0) { + return 0; + } + if (node.low == node.high) { + return count; + } + int mid = node.low + (node.high - node.low) / 2; + int leftC = node.leftCount.get(count); + if (x <= mid) { + return rank(node.left, x, leftC); + } else { + return rank(node.right, x, count - leftC); + } + } + + /** + * What is the 0-based index of the k-th occurrence of the number x in the array? + * + * @param x the number to search for + * @param k the occurrence count (1-based) + * @return the 0-based index in the original array, or -1 if x occurs less than k times + */ + public int select(int x, int k) { + if (root == null || x < root.low || x > root.high || k <= 0) { + return -1; + } + if (rank(x, n - 1) < k) { + return -1; + } + return select(root, x, k); + } + + private int select(Node node, int x, int k) { + if (node.low == node.high) { + return k - 1; // 0-based index within the imaginary array at the leaf + } + int mid = node.low + (node.high - node.low) / 2; + if (x <= mid) { + int posInLeft = select(node.left, x, k); + return binarySearchLeft(node.leftCount, posInLeft + 1); + } else { + int posInRight = select(node.right, x, k); + return binarySearchRight(node.leftCount, posInRight + 1); + } + } + + private int binarySearchLeft(List prefixSums, int k) { + int l = 1; + int r = prefixSums.size() - 1; + int ans = -1; + while (l <= r) { + int mid = l + (r - l) / 2; + if (prefixSums.get(mid) >= k) { + ans = mid; + r = mid - 1; + } else { + l = mid + 1; + } + } + return ans == -1 ? -1 : ans - 1; // Convert to 0-based index + } + + private int binarySearchRight(List prefixSums, int k) { + int l = 1; + int r = prefixSums.size() - 1; + int ans = -1; + while (l <= r) { + int mid = l + (r - l) / 2; + if (mid - prefixSums.get(mid) >= k) { + ans = mid; + r = mid - 1; + } else { + l = mid + 1; + } + } + return ans == -1 ? -1 : ans - 1; // Convert to 0-based index + } + + /** + * If you sort the subarray from index left to right, what would be the k-th smallest element? + * This query is also commonly known as the quantile query. + * + * @param left the start index of the subarray (0-based, inclusive) + * @param right the end index of the subarray (0-based, inclusive) + * @param k the rank of the smallest element (1-based, e.g., k=1 is the minimum) + * @return the k-th smallest element in the subarray, or -1 if invalid parameters + */ + public int kthSmallest(int left, int right, int k) { + if (root == null || left > right || left < 0 || k < 1 || k > right - left + 1) { + return -1; + } + return kthSmallest(root, left, right, k); + } + + private int kthSmallest(Node node, int left, int right, int k) { + if (node.low == node.high) { + return node.low; + } + + int countLeftInLMinus1 = (left == 0) ? 0 : node.leftCount.get(left); + int countLeftInR = node.leftCount.get(right + 1); + int elementsToLeft = countLeftInR - countLeftInLMinus1; + + if (k <= elementsToLeft) { + int newL = countLeftInLMinus1; + int newR = countLeftInR - 1; + return kthSmallest(node.left, newL, newR, k); + } else { + int newL = left - countLeftInLMinus1; + int newR = right - countLeftInR; + return kthSmallest(node.right, newL, newR, k - elementsToLeft); + } + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/DigitDP.java b/src/main/java/com/thealgorithms/dynamicprogramming/DigitDP.java new file mode 100644 index 000000000000..7dae7603fedc --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/DigitDP.java @@ -0,0 +1,111 @@ +package com.thealgorithms.dynamicprogramming; +import java.util.Arrays; + +/** + * A generalized template for the Digit Dynamic Programming (Digit DP) + * technique. + * Digit DP is used to count numbers within a range [L, R] that satisfy specific + * digit properties. + * This specific implementation demonstrates counting the numbers whose digit + * sum equals a target value. + * + *

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

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

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

    + *
  • {@link com.thealgorithms.maths.FibonacciLoop} - Standard Iterative (Loop) approach
  • + *
  • {@link com.thealgorithms.recursion.FibonacciSeries} - Naive Recursive approach
  • + *
  • {@link com.thealgorithms.maths.FibonacciJavaStreams} - Functional approach using Java Streams
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberGoldenRation} - Closed-form expression using Binet's formula
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberCheck} - Utility to check if a given number is a Fibonacci number
  • + *
  • {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
  • + *
+ * * @author Varun Upadhyay (https://github.com/varunu28) */ public final class Fibonacci { private Fibonacci() { diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequence.java b/src/main/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequence.java index 0b40d4559341..5c8a6a953f83 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequence.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequence.java @@ -1,58 +1,42 @@ package com.thealgorithms.dynamicprogramming; /** - * Algorithm explanation - * https://www.educative.io/edpresso/longest-palindromic-subsequence-algorithm + * Longest Palindromic Subsequence algorithm. + * A palindromic subsequence is a subsequence that reads the same forwards and backwards. + * This implementation finds the longest such subsequence by computing the LCS of the + * original string and its reverse. + * + * @see Wikipedia */ public final class LongestPalindromicSubsequence { private LongestPalindromicSubsequence() { } - public static void main(String[] args) { - String a = "BBABCBCAB"; - String b = "BABCBAB"; - - String aLPS = lps(a); - String bLPS = lps(b); - - System.out.println(a + " => " + aLPS); - System.out.println(b + " => " + bLPS); - } - - public static String lps(String original) throws IllegalArgumentException { - StringBuilder reverse = new StringBuilder(original); - reverse = reverse.reverse(); - return recursiveLPS(original, reverse.toString()); + /** + * Returns the longest palindromic subsequence of the given string. + * + * @param original the input string + * @return the longest palindromic subsequence + * @throws IllegalArgumentException if the input string is null + */ + public static String lps(String original) { + if (original == null) { + throw new IllegalArgumentException("Input string must not be null"); + } + String reverse = new StringBuilder(original).reverse().toString(); + return recursiveLPS(original, reverse); } private static String recursiveLPS(String original, String reverse) { - String bestResult = ""; - - // no more chars, then return empty - if (original.length() == 0 || reverse.length() == 0) { - bestResult = ""; - } else { - // if the last chars match, then remove it from both strings and recur - if (original.charAt(original.length() - 1) == reverse.charAt(reverse.length() - 1)) { - String bestSubResult = recursiveLPS(original.substring(0, original.length() - 1), reverse.substring(0, reverse.length() - 1)); - - bestResult = reverse.charAt(reverse.length() - 1) + bestSubResult; - } else { - // otherwise (1) ignore the last character of reverse, and recur on original and - // updated reverse again (2) ignore the last character of original and recur on the - // updated original and reverse again then select the best result from these two - // subproblems. - - String bestSubResult1 = recursiveLPS(original, reverse.substring(0, reverse.length() - 1)); - String bestSubResult2 = recursiveLPS(original.substring(0, original.length() - 1), reverse); - if (bestSubResult1.length() > bestSubResult2.length()) { - bestResult = bestSubResult1; - } else { - bestResult = bestSubResult2; - } - } + if (original.isEmpty() || reverse.isEmpty()) { + return ""; } - - return bestResult; + if (original.charAt(original.length() - 1) == reverse.charAt(reverse.length() - 1)) { + String bestSubResult = recursiveLPS(original.substring(0, original.length() - 1), reverse.substring(0, reverse.length() - 1)); + return reverse.charAt(reverse.length() - 1) + bestSubResult; + } + String sub1 = recursiveLPS(original, reverse.substring(0, reverse.length() - 1)); + String sub2 = recursiveLPS(original.substring(0, original.length() - 1), reverse); + return sub1.length() >= sub2.length() ? sub1 : sub2; } } diff --git a/src/main/java/com/thealgorithms/graph/AccountMerge.java b/src/main/java/com/thealgorithms/graph/AccountMerge.java index cf934a72eb68..86f0a24b6c1c 100644 --- a/src/main/java/com/thealgorithms/graph/AccountMerge.java +++ b/src/main/java/com/thealgorithms/graph/AccountMerge.java @@ -10,13 +10,25 @@ /** * Merges account records using Disjoint Set Union (Union-Find) on shared emails. * - *

Input format: each account is a list where the first element is the user name and the - * remaining elements are emails. + *

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

The returned list is sorted by account owner name, then by the first email address when + * multiple merged groups have the same owner name. Within each merged account, emails are + * returned in lexicographic order. + * + * @param accounts a list of accounts where each entry contains a user name followed by emails + * @return merged accounts, or an empty list when {@code accounts} is null or empty + */ public static List> mergeAccounts(List> accounts) { if (accounts == null || accounts.isEmpty()) { return List.of(); @@ -73,6 +85,9 @@ public static List> mergeAccounts(List> accounts) { return merged; } + /** + * Lightweight union-find structure with path compression and union by rank. + */ private static final class UnionFind { private final int[] parent; private final int[] rank; diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/CoinChange.java b/src/main/java/com/thealgorithms/greedyalgorithms/CoinChange.java index 8054581d21d7..5f9f6080d0e1 100644 --- a/src/main/java/com/thealgorithms/greedyalgorithms/CoinChange.java +++ b/src/main/java/com/thealgorithms/greedyalgorithms/CoinChange.java @@ -6,10 +6,30 @@ // Problem Link : https://en.wikipedia.org/wiki/Change-making_problem +/** + * The Coin Change problem finds the minimum number of coins needed + * to make a given amount using a greedy approach. + * + *

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

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

Space Complexity: O(n) + * + * @param amount the total amount to make change for + * @return list of coins used to make the amount + */ public static ArrayList coinChangeProblem(int amount) { // Define an array of coin denominations in descending order Integer[] coins = {1, 2, 5, 10, 20, 50, 100, 500, 2000}; diff --git a/src/main/java/com/thealgorithms/machinelearning/LinearRegression.java b/src/main/java/com/thealgorithms/machinelearning/LinearRegression.java new file mode 100644 index 000000000000..134d4eee8c4c --- /dev/null +++ b/src/main/java/com/thealgorithms/machinelearning/LinearRegression.java @@ -0,0 +1,102 @@ +package com.thealgorithms.machinelearning; + +/** + * A simple Linear Regression model implemented from scratch using Gradient Descent. + * + * @see Linear Regression (Wikipedia) + * @author Vraj Prajapati (Rosander0) + */ +public class LinearRegression { + private double m; // Slope (weight) + private double b; // Y-intercept (bias) + private final double learningRate; + private final int epochs; + + /** + * Constructs a Linear Regression model with the given hyperparameters. + * + * @param learningRate controls the step size during gradient descent + * @param epochs the number of iterations to train the model + */ + public LinearRegression(double learningRate, int epochs) { + this.learningRate = learningRate; + this.epochs = epochs; + this.m = 0.0; + this.b = 0.0; + } + + /** + * Trains the model on the provided dataset using batch gradient descent. + * + * @param x the input feature values + * @param y the corresponding target values + * @throws IllegalArgumentException if the arrays are null, empty, or of differing lengths + */ + public void fit(double[] x, double[] y) { + if (x == null || y == null || x.length != y.length || x.length == 0) { + throw new IllegalArgumentException("X and Y must be non-null, non-empty, and of the same length."); + } + + int n = x.length; + + for (int epoch = 0; epoch < epochs; epoch++) { + double mGradient = 0; + double bGradient = 0; + + // Calculate gradients across the entire dataset + for (int i = 0; i < n; i++) { + double prediction = (m * x[i]) + b; + double error = prediction - y[i]; + + // Partial derivatives of the Mean Squared Error cost function + mGradient += error * x[i]; + bGradient += error; + } + + // Average the gradients and update the parameters + m -= 2.0 / n * mGradient * learningRate; + b -= 2.0 / n * bGradient * learningRate; + } + } + + /** + * Predicts the output for a given input x. + * + * @param x the input value + * @return the predicted output + */ + public double predict(double x) { + return (m * x) + b; + } + + /** + * Calculates the Mean Squared Error of the model against a dataset. + * + * @param x the input feature values + * @param y the corresponding target values + * @return the mean squared error + */ + public double calculateMSE(double[] x, double[] y) { + double totalSquaredError = 0; + int n = x.length; + for (int i = 0; i < n; i++) { + double error = predict(x[i]) - y[i]; + totalSquaredError += error * error; + } + return totalSquaredError / n; + } + + /** + * @return the learned slope of the regression line + */ + public double getSlope() { + return m; + } + + /** + * @return the learned y-intercept of the regression line + */ + public double getIntercept() { + return b; + } +} diff --git a/src/main/java/com/thealgorithms/maths/AbsoluteMin.java b/src/main/java/com/thealgorithms/maths/AbsoluteMin.java index 1b9575a330dd..aab6fe0f426d 100644 --- a/src/main/java/com/thealgorithms/maths/AbsoluteMin.java +++ b/src/main/java/com/thealgorithms/maths/AbsoluteMin.java @@ -1,7 +1,5 @@ package com.thealgorithms.maths; -import java.util.Arrays; - public final class AbsoluteMin { private AbsoluteMin() { } @@ -13,14 +11,17 @@ private AbsoluteMin() { * @return The absolute min value */ public static int getMinValue(int... numbers) { - if (numbers.length == 0) { - throw new IllegalArgumentException("Numbers array cannot be empty"); + if (numbers == null || numbers.length == 0) { + throw new IllegalArgumentException("Numbers array cannot be empty or null"); } - var absMinWrapper = new Object() { int value = numbers[0]; }; - - Arrays.stream(numbers).skip(1).filter(number -> Math.abs(number) <= Math.abs(absMinWrapper.value)).forEach(number -> absMinWrapper.value = Math.min(absMinWrapper.value, number)); - - return absMinWrapper.value; + long absMin = numbers[0]; + for (int i = 1; i < numbers.length; i++) { + long current = numbers[i]; + if (Math.abs(current) < Math.abs(absMin) || (Math.abs(current) == Math.abs(absMin) && current < absMin)) { + absMin = current; + } + } + return (int) absMin; } } diff --git a/src/main/java/com/thealgorithms/maths/DisariumNumber.java b/src/main/java/com/thealgorithms/maths/DisariumNumber.java new file mode 100644 index 000000000000..0196d0797bcc --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/DisariumNumber.java @@ -0,0 +1,37 @@ +package com.thealgorithms.maths; + +/** + * Disarium number is a number where the sum of its digits powered + * with their respective positions is equal to the number itself. + * Example: 135 = 1^1 + 3^2 + 5^3 = 1 + 9 + 125 = 135 + * + * @see Disarium Number + */ +public final class DisariumNumber { + + private DisariumNumber() { + } + + /** + * Checks if a number is a Disarium number. + * + * @param number the number to check (must be positive) + * @return true if number is Disarium, false otherwise + * @throws IllegalArgumentException if number is not positive + */ + public static boolean isDisarium(int number) { + if (number <= 0) { + throw new IllegalArgumentException("Input must be a positive integer."); + } + int digits = String.valueOf(number).length(); + int temp = number; + int sum = 0; + while (temp > 0) { + int lastDigit = temp % 10; + sum += (int) Math.pow(lastDigit, digits); + digits--; + temp /= 10; + } + return sum == number; + } +} diff --git a/src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java b/src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java index 84390860ccc4..8a93580a4b72 100644 --- a/src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java +++ b/src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java @@ -6,9 +6,23 @@ import java.util.stream.Stream; /** - * @author: caos321 - * @date: 14 October 2021 (Thursday) + * Calculates Fibonacci numbers using a functional programming paradigm with Java Streams. + *

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

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

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

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

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

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

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

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

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

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

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

    + *
  • {@link com.thealgorithms.maths.FibonacciLoop} - Standard Iterative (Loop) approach
  • + *
  • {@link com.thealgorithms.recursion.FibonacciSeries} - Naive Recursive approach
  • + *
  • {@link com.thealgorithms.dynamicprogramming.Fibonacci} - Dynamic Programming approaches (Memoization, Bottom-Up, Optimized)
  • + *
  • {@link com.thealgorithms.maths.FibonacciJavaStreams} - Functional approach using Java Streams
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberCheck} - Utility to check if a given number is a Fibonacci number
  • + *
  • {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
  • + *
* * @see Binet's formula on Wikipedia */ diff --git a/src/main/java/com/thealgorithms/maths/FriendlyNumber.java b/src/main/java/com/thealgorithms/maths/FriendlyNumber.java new file mode 100644 index 000000000000..900ce89295a4 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/FriendlyNumber.java @@ -0,0 +1,49 @@ +package com.thealgorithms.maths; + +/** + * Two numbers are Friendly if they share the same abundancy index, + * which is the ratio of the sum of divisors to the number itself. + * Example: 6 and 28 are friendly because sigma(6)/6 = 2 and sigma(28)/28 = 2 + * + * @see + * Wikipedia: Friendly Number + * + * @author Vraj Prajapati @Rosander0 + */ +public final class FriendlyNumber { + + private FriendlyNumber() { + // Utility class + } + + private static int sumOfDivisors(final int number) { + int sum = 0; + final int root = (int) Math.sqrt(number); + for (int i = 1; i <= root; i++) { + if (number % i == 0) { + sum += i; + final int other = number / i; + if (other != i) { + sum += other; + } + } + } + return sum; + } + + /** + * Checks whether two numbers are Friendly Numbers. + * + * @param a First number (must be positive) + * @param b Second number (must be positive) + * @return true if a and b are friendly numbers, false otherwise + */ + public static boolean areFriendly(final int a, final int b) { + if (a <= 0 || b <= 0) { + return false; + } + final long sigmaA = sumOfDivisors(a); + final long sigmaB = sumOfDivisors(b); + return sigmaA * b == sigmaB * a; + } +} diff --git a/src/main/java/com/thealgorithms/maths/JacobsthalNumber.java b/src/main/java/com/thealgorithms/maths/JacobsthalNumber.java new file mode 100644 index 000000000000..f4f2e23c3932 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/JacobsthalNumber.java @@ -0,0 +1,44 @@ +package com.thealgorithms.maths; +// author: Vraj Prajapati @Rosander0 + +/** + * The Jacobsthal Sequence is a sequence of integers defined by the recurrence relation: + * J(n) = J(n-1) + 2*J(n-2) with initial values J(0) = 0, J(1) = 1. + * Example: 0, 1, 1, 3, 5, 11, 21, 43, 85, 171, 341... + * + * @see + * Wikipedia: Jacobsthal Number + */ +public final class JacobsthalNumber { + + private JacobsthalNumber() { + // Utility class + } + + /** + * Calculates the nth term of the Jacobsthal Sequence. + * + * @param n the index of the sequence (must be non-negative) + * @return the nth term of the Jacobsthal Sequence + */ + public static long jacobsthal(final int n) { + if (n < 0) { + throw new IllegalArgumentException("Input must be non-negative!"); + } + if (n == 0) { + return 0; + } + if (n == 1) { + return 1; + } + long a = 0; + long b = 1; + long result = 0; + for (int i = 2; i <= n; i++) { + result = b + 2 * a; + a = b; + b = result; + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java b/src/main/java/com/thealgorithms/maths/LowestBasePalindrome.java similarity index 94% rename from src/main/java/com/thealgorithms/others/LowestBasePalindrome.java rename to src/main/java/com/thealgorithms/maths/LowestBasePalindrome.java index a3ca8d6f6db8..4a79b4298fc4 100644 --- a/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java +++ b/src/main/java/com/thealgorithms/maths/LowestBasePalindrome.java @@ -1,4 +1,4 @@ -package com.thealgorithms.others; +package com.thealgorithms.maths; import java.util.ArrayList; import java.util.List; @@ -23,6 +23,12 @@ * * @see OEIS A016026 - Smallest base in which * n is palindromic + * @see com.thealgorithms.strings.Palindrome + * @see com.thealgorithms.stacks.PalindromeWithStack + * @see com.thealgorithms.bitmanipulation.BinaryPalindromeCheck + * @see com.thealgorithms.datastructures.lists.PalindromeSinglyLinkedList + * @see com.thealgorithms.maths.PalindromePrime + * @see com.thealgorithms.maths.PalindromeNumber * @author TheAlgorithms Contributors */ public final class LowestBasePalindrome { diff --git a/src/main/java/com/thealgorithms/maths/PadovanSequence.java b/src/main/java/com/thealgorithms/maths/PadovanSequence.java new file mode 100644 index 000000000000..51e7d2441b15 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/PadovanSequence.java @@ -0,0 +1,43 @@ +package com.thealgorithms.maths; + +/** + * The Padovan Sequence is a sequence of integers defined by the recurrence relation: + * P(n) = P(n-2) + P(n-3) with initial values P(0) = P(1) = P(2) = 1. + * Example: 1, 1, 1, 2, 2, 3, 4, 5, 7, 9, 12, 16, 21, 28, 37... + * + * @see + * Wikipedia: Padovan Sequence + * @author Vraj Prajapati (@Rosander0) + */ +public final class PadovanSequence { + + private PadovanSequence() { + // Utility class + } + + /** + * Calculates the nth term of the Padovan Sequence. + * + * @param n the index of the sequence (must be non-negative) + * @return the nth term of the Padovan Sequence + */ + public static long padovan(final int n) { + if (n < 0) { + throw new IllegalArgumentException("Input must be non-negative. Received: " + n); + } + if (n <= 2) { + return 1; + } + long a = 1; + long b = 1; + long c = 1; + long result = 0; + for (int i = 3; i <= n; i++) { + result = a + b; + a = b; + b = c; + c = result; + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/maths/PalindromeNumber.java b/src/main/java/com/thealgorithms/maths/PalindromeNumber.java index a22d63897b37..9543f83332a7 100644 --- a/src/main/java/com/thealgorithms/maths/PalindromeNumber.java +++ b/src/main/java/com/thealgorithms/maths/PalindromeNumber.java @@ -1,5 +1,16 @@ package com.thealgorithms.maths; +/** + * A class to check if a given number is a palindrome. + * A palindromic number is a number that remains the same when its digits are reversed. + * + * @see com.thealgorithms.strings.Palindrome + * @see com.thealgorithms.stacks.PalindromeWithStack + * @see com.thealgorithms.bitmanipulation.BinaryPalindromeCheck + * @see com.thealgorithms.maths.LowestBasePalindrome + * @see com.thealgorithms.datastructures.lists.PalindromeSinglyLinkedList + * @see com.thealgorithms.maths.PalindromePrime + */ public final class PalindromeNumber { private PalindromeNumber() { } diff --git a/src/main/java/com/thealgorithms/misc/PalindromePrime.java b/src/main/java/com/thealgorithms/maths/PalindromePrime.java similarity index 73% rename from src/main/java/com/thealgorithms/misc/PalindromePrime.java rename to src/main/java/com/thealgorithms/maths/PalindromePrime.java index 164e957a9d12..21b76acefee8 100644 --- a/src/main/java/com/thealgorithms/misc/PalindromePrime.java +++ b/src/main/java/com/thealgorithms/maths/PalindromePrime.java @@ -1,8 +1,19 @@ -package com.thealgorithms.misc; +package com.thealgorithms.maths; import java.util.ArrayList; import java.util.List; +/** + * A class to check and generate palindromic prime numbers. + * A palindromic prime is a prime number that is also a palindromic number. + * + * @see com.thealgorithms.strings.Palindrome + * @see com.thealgorithms.stacks.PalindromeWithStack + * @see com.thealgorithms.bitmanipulation.BinaryPalindromeCheck + * @see com.thealgorithms.maths.LowestBasePalindrome + * @see com.thealgorithms.datastructures.lists.PalindromeSinglyLinkedList + * @see com.thealgorithms.maths.PalindromeNumber + */ public final class PalindromePrime { private PalindromePrime() { } diff --git a/src/main/java/com/thealgorithms/maths/PerrinNumber.java b/src/main/java/com/thealgorithms/maths/PerrinNumber.java new file mode 100644 index 000000000000..cee45a1c5538 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/PerrinNumber.java @@ -0,0 +1,53 @@ +package com.thealgorithms.maths; +// author: Vraj Prajapati @Rosander0 + +/** + * The Perrin Sequence is a sequence of integers defined by the recurrence relation: + * P(n) = P(n-2) + P(n-3) with initial values P(0) = 3, P(1) = 0, P(2) = 2. + * Example: 3, 0, 2, 3, 2, 5, 5, 7, 10, 12, 17, 22, 29, 39, 51... + * + * Note: The Perrin Sequence uses the same recurrence relation as the Padovan Sequence + * but has different initial values. + * + * @see + * Wikipedia: Perrin Number + * @see PadovanSequence + */ +public final class PerrinNumber { + + private PerrinNumber() { + // Utility class + } + + /** + * Calculates the nth term of the Perrin Sequence. + * + * @param n the index of the sequence (must be non-negative) + * @return the nth term of the Perrin Sequence + */ + public static long perrin(final int n) { + if (n < 0) { + throw new IllegalArgumentException("Input must be non-negative!"); + } + if (n == 0) { + return 3; + } + if (n == 1) { + return 0; + } + if (n == 2) { + return 2; + } + long a = 3; + long b = 0; + long c = 2; + long result = 0; + for (int i = 3; i <= n; i++) { + result = a + b; + a = b; + b = c; + c = result; + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/maths/SociableNumber.java b/src/main/java/com/thealgorithms/maths/SociableNumber.java new file mode 100644 index 000000000000..9ce644e61dfc --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/SociableNumber.java @@ -0,0 +1,67 @@ +package com.thealgorithms.maths; + +/** + * Sociable numbers are natural numbers that form a cyclic sequence where the + * sum of proper divisors of each number equals the next number in the sequence, + * with the sequence eventually returning to the starting number. + * Amicable numbers are a special case of sociable numbers with a cycle length of 2. + * Example: (12496, 14288, 15472, 14536, 14264) is a sociable cycle of length 5. + * + * @author Vraj Prajapati (@Rosander0) + * @see Wikipedia: Sociable Number + * @see AmicableNumber + */ +public final class SociableNumber { + + private SociableNumber() { + // Utility class + } + + /** + * Calculates the sum of proper divisors of a number + * (all divisors excluding the number itself). + * + * @param number the number to calculate proper divisors sum for + * @return sum of proper divisors, or 0 if number is less than or equal to 1 + */ + static int sumOfProperDivisors(final int number) { + if (number <= 1) { + return 0; + } + int sum = 1; // 1 is a proper divisor of every number > 1 + final int root = (int) Math.sqrt(number); + for (int i = 2; i <= root; i++) { + if (number % i == 0) { + final int other = number / i; + sum += i; + if (other != i) { + sum += other; + } + } + } + return sum; + } + + /** + * Checks whether a number is part of a sociable cycle of a given length. + * Starting from the given number, it follows the chain of proper divisor + * sums and checks if it returns to the starting number in exactly cycleLength steps. + * + * @param number the starting number (must be positive) + * @param cycleLength the expected cycle length (must be greater than 1) + * @return true if the number is part of a sociable cycle of given length, false otherwise + */ + public static boolean isSociable(final int number, final int cycleLength) { + if (number <= 0 || cycleLength <= 1) { + return false; + } + int current = number; + for (int i = 0; i < cycleLength; i++) { + current = sumOfProperDivisors(current); + if (current == number) { + return i == cycleLength - 1; + } + } + return false; + } +} diff --git a/src/main/java/com/thealgorithms/matrix/QRDecomposition.java b/src/main/java/com/thealgorithms/matrix/QRDecomposition.java new file mode 100644 index 000000000000..45f56bc14729 --- /dev/null +++ b/src/main/java/com/thealgorithms/matrix/QRDecomposition.java @@ -0,0 +1,149 @@ +package com.thealgorithms.matrix; + +/** + * @brief Implementation of QR Decomposition using the Gram-Schmidt process + * @details Decomposes a matrix A into an orthogonal matrix Q and an upper + * triangular matrix R such that A = Q * R. The Gram-Schmidt process + * orthogonalizes the columns of A to produce Q, and R is computed as Q^T * A. + * This decomposition is useful for solving linear least squares problems, + * eigenvalue computations, and numerical stability in linear algebra. + * @see QR Decomposition + */ +public final class QRDecomposition { + + private QRDecomposition() { + } + + /** + * A helper class to store both Q and R matrices + */ + public static class QR { + private final double[][] q; + private final double[][] r; + + QR(double[][] q, double[][] r) { + this.q = q; + this.r = r; + } + + public double[][] getQ() { + return q; + } + + public double[][] getR() { + return r; + } + } + + /** + * @brief Performs QR decomposition on a matrix using the Gram-Schmidt process + * @param matrix the input matrix (m x n) + * @return QR object containing orthogonal matrix Q (m x n) and upper triangular matrix R (n x n) + * @throws IllegalArgumentException if the matrix is null, empty, or has invalid rows + */ + public static QR decompose(double[][] matrix) { + validateInputMatrix(matrix); + + int m = matrix.length; + int n = matrix[0].length; + + double[][] q = new double[m][n]; + double[][] r = new double[n][n]; + + for (int j = 0; j < n; j++) { + double[] v = getColumn(matrix, j); + + for (int i = 0; i < j; i++) { + double[] qi = getColumn(q, i); + r[i][j] = dotProduct(qi, v); + v = subtractVectors(v, scalarMultiply(qi, r[i][j])); + } + + r[j][j] = norm(v); + if (r[j][j] == 0) { + throw new ArithmeticException("Matrix is rank deficient. Cannot perform QR decomposition."); + } + double[] qj = scalarMultiply(v, 1.0 / r[j][j]); + setColumn(q, j, qj); + } + + return new QR(q, r); + } + + private static double[] getColumn(double[][] matrix, int col) { + int m = matrix.length; + double[] column = new double[m]; + for (int i = 0; i < m; i++) { + column[i] = matrix[i][col]; + } + return column; + } + + private static void setColumn(double[][] matrix, int col, double[] column) { + for (int i = 0; i < matrix.length; i++) { + matrix[i][col] = column[i]; + } + } + + private static double dotProduct(double[] a, double[] b) { + double sum = 0; + for (int i = 0; i < a.length; i++) { + sum += a[i] * b[i]; + } + return sum; + } + + private static double[] subtractVectors(double[] a, double[] b) { + double[] result = new double[a.length]; + for (int i = 0; i < a.length; i++) { + result[i] = a[i] - b[i]; + } + return result; + } + + private static double[] scalarMultiply(double[] v, double scalar) { + double[] result = new double[v.length]; + for (int i = 0; i < v.length; i++) { + result[i] = v[i] * scalar; + } + return result; + } + + private static double norm(double[] v) { + return Math.sqrt(dotProduct(v, v)); + } + + private static void validateInputMatrix(double[][] matrix) { + if (matrix == null) { + throw new IllegalArgumentException("The input matrix cannot be null"); + } + if (matrix.length == 0) { + throw new IllegalArgumentException("The input matrix cannot be empty"); + } + if (!hasValidRows(matrix)) { + throw new IllegalArgumentException("The input matrix cannot have null or empty rows"); + } + if (isJaggedMatrix(matrix)) { + throw new IllegalArgumentException("The input matrix cannot be jagged"); + } + } + + private static boolean hasValidRows(double[][] matrix) { + for (double[] row : matrix) { + if (row == null || row.length == 0) { + return false; + } + } + return true; + } + + private static boolean isJaggedMatrix(double[][] matrix) { + int numColumns = matrix[0].length; + for (double[] row : matrix) { + if (row.length != numColumns) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/com/thealgorithms/others/Dijkstra.java b/src/main/java/com/thealgorithms/others/Dijkstra.java deleted file mode 100644 index a379100a2f3b..000000000000 --- a/src/main/java/com/thealgorithms/others/Dijkstra.java +++ /dev/null @@ -1,248 +0,0 @@ -package com.thealgorithms.others; - -import java.util.HashMap; -import java.util.Map; -import java.util.NavigableSet; -import java.util.TreeSet; -/** - * Dijkstra's algorithm,is a graph search algorithm that solves the - * single-source shortest path problem for a graph with nonnegative edge path - * costs, producing a shortest path tree. - * - *

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

- * Original source of code: - * https://rosettacode.org/wiki/Dijkstra%27s_algorithm#Java Also most of the - * comments are from RosettaCode. - */ -public final class Dijkstra { - private Dijkstra() { - } - - private static final Graph.Edge[] GRAPH = { - // Distance from node "a" to node "b" is 7. - // In the current Graph there is no way to move the other way (e,g, from "b" to "a"), - // a new edge would be needed for that - new Graph.Edge("a", "b", 7), - new Graph.Edge("a", "c", 9), - new Graph.Edge("a", "f", 14), - new Graph.Edge("b", "c", 10), - new Graph.Edge("b", "d", 15), - new Graph.Edge("c", "d", 11), - new Graph.Edge("c", "f", 2), - new Graph.Edge("d", "e", 6), - new Graph.Edge("e", "f", 9), - }; - private static final String START = "a"; - private static final String END = "e"; - - /** - * main function Will run the code with "GRAPH" that was defined above. - */ - public static void main(String[] args) { - Graph g = new Graph(GRAPH); - g.dijkstra(START); - g.printPath(END); - // g.printAllPaths(); - } -} - -class Graph { - - // mapping of vertex names to Vertex objects, built from a set of Edges - - private final Map graph; - - /** - * One edge of the graph (only used by Graph constructor) - */ - public static class Edge { - - public final String v1; - public final String v2; - public final int dist; - - Edge(String v1, String v2, int dist) { - this.v1 = v1; - this.v2 = v2; - this.dist = dist; - } - } - - /** - * One vertex of the graph, complete with mappings to neighbouring vertices - */ - public static class Vertex implements Comparable { - - public final String name; - // MAX_VALUE assumed to be infinity - public int dist = Integer.MAX_VALUE; - public Vertex previous = null; - public final Map neighbours = new HashMap<>(); - - Vertex(String name) { - this.name = name; - } - - private void printPath() { - if (this == this.previous) { - System.out.printf("%s", this.name); - } else if (this.previous == null) { - System.out.printf("%s(unreached)", this.name); - } else { - this.previous.printPath(); - System.out.printf(" -> %s(%d)", this.name, this.dist); - } - } - - public int compareTo(Vertex other) { - if (dist == other.dist) { - return name.compareTo(other.name); - } - - return Integer.compare(dist, other.dist); - } - - @Override - public boolean equals(Object object) { - if (this == object) { - return true; - } - if (object == null || getClass() != object.getClass()) { - return false; - } - if (!super.equals(object)) { - return false; - } - - Vertex vertex = (Vertex) object; - - if (dist != vertex.dist) { - return false; - } - if (name != null ? !name.equals(vertex.name) : vertex.name != null) { - return false; - } - if (previous != null ? !previous.equals(vertex.previous) : vertex.previous != null) { - return false; - } - return neighbours != null ? neighbours.equals(vertex.neighbours) : vertex.neighbours == null; - } - - @Override - public int hashCode() { - int result = super.hashCode(); - result = 31 * result + (name != null ? name.hashCode() : 0); - result = 31 * result + dist; - result = 31 * result + (previous != null ? previous.hashCode() : 0); - result = 31 * result + (neighbours != null ? neighbours.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return "(" + name + ", " + dist + ")"; - } - } - - /** - * Builds a graph from a set of edges - */ - Graph(Edge[] edges) { - graph = new HashMap<>(edges.length); - - // one pass to find all vertices - for (Edge e : edges) { - if (!graph.containsKey(e.v1)) { - graph.put(e.v1, new Vertex(e.v1)); - } - if (!graph.containsKey(e.v2)) { - graph.put(e.v2, new Vertex(e.v2)); - } - } - - // another pass to set neighbouring vertices - for (Edge e : edges) { - graph.get(e.v1).neighbours.put(graph.get(e.v2), e.dist); - // graph.get(e.v2).neighbours.put(graph.get(e.v1), e.dist); // also do this for an - // undirected graph - } - } - - /** - * Runs dijkstra using a specified source vertex - */ - public void dijkstra(String startName) { - if (!graph.containsKey(startName)) { - System.err.printf("Graph doesn't contain start vertex \"%s\"%n", startName); - return; - } - final Vertex source = graph.get(startName); - NavigableSet q = new TreeSet<>(); - - // set-up vertices - for (Vertex v : graph.values()) { - v.previous = v == source ? source : null; - v.dist = v == source ? 0 : Integer.MAX_VALUE; - q.add(v); - } - - dijkstra(q); - } - - /** - * Implementation of dijkstra's algorithm using a binary heap. - */ - private void dijkstra(final NavigableSet q) { - Vertex u; - Vertex v; - while (!q.isEmpty()) { - // vertex with shortest distance (first iteration will return source) - u = q.pollFirst(); - if (u.dist == Integer.MAX_VALUE) { - break; // we can ignore u (and any other remaining vertices) since they are - // unreachable - } - // look at distances to each neighbour - for (Map.Entry a : u.neighbours.entrySet()) { - v = a.getKey(); // the neighbour in this iteration - - final int alternateDist = u.dist + a.getValue(); - if (alternateDist < v.dist) { // shorter path to neighbour found - q.remove(v); - v.dist = alternateDist; - v.previous = u; - q.add(v); - } - } - } - } - - /** - * Prints a path from the source to the specified vertex - */ - public void printPath(String endName) { - if (!graph.containsKey(endName)) { - System.err.printf("Graph doesn't contain end vertex \"%s\"%n", endName); - return; - } - - graph.get(endName).printPath(); - System.out.println(); - } - - /** - * Prints the path from the source to every vertex (output order is not - * guaranteed) - */ - public void printAllPaths() { - for (Vertex v : graph.values()) { - v.printPath(); - System.out.println(); - } - } -} diff --git a/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java b/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java index 9c809858099e..404adfdf0cf1 100644 --- a/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java +++ b/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java @@ -7,8 +7,18 @@ * Example: * 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 ... *

+ *

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

    + *
  • {@link com.thealgorithms.maths.FibonacciLoop} - Standard Iterative (Loop) approach
  • + *
  • {@link com.thealgorithms.dynamicprogramming.Fibonacci} - Dynamic Programming variants (Memoization / Bottom-Up)
  • + *
  • {@link com.thealgorithms.maths.FibonacciJavaStreams} - Functional approach using Java Streams
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberGoldenRation} - Closed-form expression using Binet's formula
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberCheck} - Utility to check if a given number is a Fibonacci number
  • + *
  • {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
  • + *
*/ - public final class FibonacciSeries { private FibonacciSeries() { throw new UnsupportedOperationException("Utility class"); diff --git a/src/main/java/com/thealgorithms/searches/InterpolationSearch.java b/src/main/java/com/thealgorithms/searches/InterpolationSearch.java index d24cc1c774bc..52dbc0b7e2c5 100644 --- a/src/main/java/com/thealgorithms/searches/InterpolationSearch.java +++ b/src/main/java/com/thealgorithms/searches/InterpolationSearch.java @@ -1,14 +1,4 @@ -/** - * Interpolation Search estimates the position of the target value - * based on the distribution of values. - * - * Example: - * Input: [10, 20, 30, 40], target = 30 - * Output: Index = 2 - * - * Time Complexity: O(log log n) (average case) - * Space Complexity: O(1) - */ + package com.thealgorithms.searches; /** @@ -46,9 +36,12 @@ public int find(int[] array, int key) { // Since array is sorted, an element present // in array must be in range defined by corner while (start <= end && key >= array[start] && key <= array[end]) { + if (array[start] == array[end]) { + return start; + } // Probing the position with keeping // uniform distribution in mind. - int pos = start + (((end - start) / (array[end] - array[start])) * (key - array[start])); + int pos = start + (int) (((long) (end - start) * (key - array[start])) / ((long) array[end] - array[start])); // Condition of target found if (array[pos] == key) { diff --git a/src/main/java/com/thealgorithms/searches/LinearSearch.java b/src/main/java/com/thealgorithms/searches/LinearSearch.java index 3f273e167f0a..c5f6e6ba9776 100644 --- a/src/main/java/com/thealgorithms/searches/LinearSearch.java +++ b/src/main/java/com/thealgorithms/searches/LinearSearch.java @@ -1,16 +1,4 @@ -/** - * Performs Linear Search on an array. - * - * Linear search checks each element one by one until the target is found - * or the array ends. - * - * Example: - * Input: [2, 4, 6, 8], target = 6 - * Output: Index = 2 - * - * Time Complexity: O(n) - * Space Complexity: O(1) - */ + package com.thealgorithms.searches; import com.thealgorithms.devutils.searches.SearchAlgorithm; @@ -22,10 +10,34 @@ * * It works for both sorted and unsorted arrays. * + *

How it works step-by-step: + *

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

Example: + *

+ *   Input array: [5, 3, 8, 1, 9]
+ *   Target: 8
+ *
+ *   Step 1: Compare 5 with 8 → no match, move on
+ *   Step 2: Compare 3 with 8 → no match, move on
+ *   Step 3: Compare 8 with 8 → match found at index 2!
+ *
+ *   Output: 2
+ *
+ *   If target = 7:
+ *   Output: -1 (not found)
+ * 
* Time Complexity: - * - Best case: O(1) - * - Average case: O(n) - * - Worst case: O(n) + * - Best case: O(1) - target is the first element + * - Average case: O(n) - target is somewhere in the middle + * - Worst case: O(n) - target is last or not present * * Space Complexity: O(1) * @@ -37,9 +49,10 @@ public class LinearSearch implements SearchAlgorithm { /** - * Generic Linear search method + * Generic Linear search method that searches for a value + * in the given array by checking each element one by one. * - * @param array List to be searched + * @param array List to be searched (can be unsorted) * @param value Key being searched for * @return Location of the key, -1 if array is null or empty, or key not found */ diff --git a/src/main/java/com/thealgorithms/slidingwindow/CountDistinctElementsInWindow.java b/src/main/java/com/thealgorithms/slidingwindow/CountDistinctElementsInWindow.java new file mode 100644 index 000000000000..19e573437f6d --- /dev/null +++ b/src/main/java/com/thealgorithms/slidingwindow/CountDistinctElementsInWindow.java @@ -0,0 +1,57 @@ +package com.thealgorithms.slidingwindow; + +import java.util.HashMap; +import java.util.Map; + +/** + * Counts the number of distinct elements in every window of size k. + * + * @see Reference + */ +public final class CountDistinctElementsInWindow { + + private CountDistinctElementsInWindow() { + } + + /** + * Returns an array where each element is the count of distinct + * elements in the corresponding window of size k. + * + * @param arr the input array + * @param k the window size + * @return array of distinct element counts per window + */ + public static int[] countDistinct(int[] arr, int k) { + if (arr == null || arr.length == 0 || k <= 0 || k > arr.length) { + throw new IllegalArgumentException("Invalid input"); + } + + int n = arr.length; + int[] result = new int[n - k + 1]; + Map freqMap = new HashMap<>(); + + for (int i = 0; i < k; i++) { + freqMap.merge(arr[i], 1, Integer::sum); + } + result[0] = freqMap.size(); + + for (int i = k; i < n; i++) { + freqMap.merge(arr[i], 1, Integer::sum); + + int outgoing = arr[i - k]; + + Integer count = freqMap.get(outgoing); + if (count != null) { + if (count == 1) { + freqMap.remove(outgoing); + } else { + freqMap.put(outgoing, count - 1); + } + } + + result[i - k + 1] = freqMap.size(); + } + + return result; + } +} diff --git a/src/main/java/com/thealgorithms/sorts/LibrarySort.java b/src/main/java/com/thealgorithms/sorts/LibrarySort.java new file mode 100644 index 000000000000..28f16e3016ff --- /dev/null +++ b/src/main/java/com/thealgorithms/sorts/LibrarySort.java @@ -0,0 +1,207 @@ +package com.thealgorithms.sorts; + +import java.util.Arrays; + +/** + * Library Sort (also known as Gapped Insertion Sort) maintains a sparse + * working array with gaps distributed between elements, so that most + * insertions land directly in an empty gap without shifting anything. + * Elements are inserted in rounds that double in size (1, 2, 4, 8, ...); + * after each round the array is rebalanced so gaps are spread out evenly + * again for the next round. + * Time Complexity: O(n log n) expected, O(n^2) worst case if gaps collapse + * Space Complexity: O(n) + * + * @see + * Wikipedia: Library Sort + * @author Vraj Prajapati (@Rosander0) + */ +public final class LibrarySort { + + private static final int GAP_FACTOR = 2; + + private LibrarySort() { + // Utility class + } + + /** + * Sorts an array using the Library Sort algorithm. + * + * @param array the array to sort (must not be null) + * @return the sorted array + * @throws IllegalArgumentException if {@code array} is {@code null} + */ + public static int[] sort(final int[] array) { + if (array == null) { + throw new IllegalArgumentException("Input array must not be null."); + } + if (array.length <= 1) { + return array; + } + + final int n = array.length; + final int capacity = GAP_FACTOR * n; + final int[] data = new int[capacity]; + final boolean[] occupied = new boolean[capacity]; + + final int mid = capacity / 2; + data[mid] = array[0]; + occupied[mid] = true; + + int filled = 1; + int nextToInsert = 1; + int round = 0; + while (nextToInsert < n) { + final int roundSize = Math.min(1 << round, n - nextToInsert); + for (int i = 0; i < roundSize; i++) { + insert(data, occupied, array[nextToInsert + i]); + filled++; + } + nextToInsert += roundSize; + round++; + if (nextToInsert < n) { + rebalance(data, occupied, filled); + } + } + + int idx = 0; + for (int i = 0; i < capacity; i++) { + if (occupied[i]) { + array[idx++] = data[i]; + } + } + return array; + } + + /** + * Inserts {@code value} into the gapped array, placing it directly in an + * empty gap when possible, otherwise shifting toward the nearest gap. + */ + private static void insert(final int[] data, final boolean[] occupied, final int value) { + final int pos = findInsertionIndex(data, occupied, value); + if (pos >= data.length) { + insertAtEnd(data, occupied, value); + return; + } + + if (!occupied[pos]) { + data[pos] = value; + occupied[pos] = true; + return; + } + + int right = pos; + while (right < data.length && occupied[right]) { + right++; + } + int left = pos - 1; + while (left >= 0 && occupied[left]) { + left--; + } + + final boolean canGoRight = right < data.length; + final boolean canGoLeft = left >= 0; + + if (canGoRight && (!canGoLeft || (right - pos) <= (pos - left))) { + // Shift data[pos, right) one slot to the right, opening a gap at pos. + // occupied[pos] is untouched by the copy and was already true. + System.arraycopy(data, pos, data, pos + 1, right - pos); + occupied[right] = true; + data[pos] = value; + } else if (canGoLeft) { + // Shift data[left + 1, pos) one slot to the left, opening a gap at pos - 1. + // occupied[pos - 1] is untouched by the copy and was already true. + System.arraycopy(data, left + 1, data, left, pos - 1 - left); + occupied[left] = true; + data[pos - 1] = value; + } else { + // Unreachable in practice: canGoRight and canGoLeft can only both be false if + // every slot in this capacity-2n array is occupied, but at most n elements are + // ever present at once. Kept as a defensive guard against that invariant breaking. + throw new IllegalStateException("No gap available for insertion; rebalance too infrequent."); + } + } + + /** + * Handles insertion of a new global maximum, which must land after every + * currently occupied slot. Since there is no room to its right, this + * shifts occupied slots left into the nearest gap instead. + */ + private static void insertAtEnd(final int[] data, final boolean[] occupied, final int value) { + final int last = data.length - 1; + // occupied[last] is unreachable as false here: insertAtEnd() is only called when + // findInsertionIndex() returns data.length, which requires data[last] to already be + // occupied. Kept as a defensive guard in case that invariant is ever broken. + if (!occupied[last]) { + data[last] = value; + occupied[last] = true; + return; + } + int left = last - 1; + while (left >= 0 && occupied[left]) { + left--; + } + // left < 0 is unreachable in practice: at most n elements ever occupy this + // capacity-2n array, so fewer than half the slots left of `last` can be filled, + // guaranteeing a gap exists before the scan reaches index -1. + if (left < 0) { + throw new IllegalStateException("No gap available for insertion; rebalance too infrequent."); + } + // Shift data[left + 1, last] one slot to the left, opening a gap at last. + // occupied[last] is untouched by the copy and was already true. + System.arraycopy(data, left + 1, data, left, last - left); + occupied[left] = true; + data[last] = value; + } + + /** + * Finds the leftmost index at which {@code value} can be inserted so + * that occupied slots remain sorted. Empty slots are compared using the + * value of the nearest occupied slot at or after them, which is a + * monotonic function of index and therefore safe to binary search over. + */ + private static int findInsertionIndex(final int[] data, final boolean[] occupied, final int value) { + int lo = 0; + int hi = data.length; + while (lo < hi) { + final int mid = lo + (hi - lo) / 2; + final int probe = nearestOccupiedValueAtOrAfter(data, occupied, mid); + if (probe != Integer.MAX_VALUE && probe <= value) { + lo = mid + 1; + } else { + hi = mid; + } + } + return lo; + } + + private static int nearestOccupiedValueAtOrAfter(final int[] data, final boolean[] occupied, final int index) { + for (int i = index; i < data.length; i++) { + if (occupied[i]) { + return data[i]; + } + } + return Integer.MAX_VALUE; + } + + /** + * Redistributes the {@code filled} occupied elements evenly across the + * full capacity of {@code data}, restoring uniform gaps between them. + */ + private static void rebalance(final int[] data, final boolean[] occupied, final int filled) { + final int capacity = data.length; + final int[] temp = new int[filled]; + int idx = 0; + for (int i = 0; i < capacity; i++) { + if (occupied[i]) { + temp[idx++] = data[i]; + } + } + Arrays.fill(occupied, false); + for (int k = 0; k < filled; k++) { + final int pos = (int) ((long) k * capacity / filled); + data[pos] = temp[k]; + occupied[pos] = true; + } + } +} diff --git a/src/main/java/com/thealgorithms/sorts/SelectionSort.java b/src/main/java/com/thealgorithms/sorts/SelectionSort.java index 2d1814441701..e6d1a16a7af7 100644 --- a/src/main/java/com/thealgorithms/sorts/SelectionSort.java +++ b/src/main/java/com/thealgorithms/sorts/SelectionSort.java @@ -1,5 +1,10 @@ package com.thealgorithms.sorts; +/** + * Implementation of the Selection Sort algorithm. + * + * @see SortAlgorithm + */ public class SelectionSort implements SortAlgorithm { /** * Generic Selection Sort algorithm. @@ -11,11 +16,12 @@ public class SelectionSort implements SortAlgorithm { * * Space Complexity: O(1) – in-place sorting. * - * @see SortAlgorithm + * @param array the array to be sorted + * @param the type of elements in the array + * @return the sorted array */ @Override public > T[] sort(T[] array) { - for (int i = 0; i < array.length - 1; i++) { final int minIndex = findIndexOfMin(array, i); SortUtils.swap(array, i, minIndex); @@ -23,6 +29,14 @@ public > T[] sort(T[] array) { return array; } + /** + * Finds the index of the minimum element in the array starting from a given index. + * + * @param array the array to search + * @param startIndex the index to start searching from + * @param the type of elements in the array + * @return the index of the minimum element + */ private static > int findIndexOfMin(T[] array, final int startIndex) { int minIndex = startIndex; for (int i = startIndex + 1; i < array.length; i++) { diff --git a/src/main/java/com/thealgorithms/sorts/WiggleSort.java b/src/main/java/com/thealgorithms/sorts/WiggleSort.java index c272b820d07a..0349971d1c95 100644 --- a/src/main/java/com/thealgorithms/sorts/WiggleSort.java +++ b/src/main/java/com/thealgorithms/sorts/WiggleSort.java @@ -11,9 +11,9 @@ * https://cs.stackexchange.com/questions/125372/how-to-wiggle-sort-an-array-in-linear-time-complexity * Also have a look at: * https://cs.stackexchange.com/questions/125372/how-to-wiggle-sort-an-array-in-linear-time-complexity?noredirect=1&lq=1 - * Not all arrays are wiggle-sortable. This algorithm will find some obviously not wiggle-sortable - * arrays and throw an error, but there are some exceptions that won't be caught, for example [1, 2, - * 2]. + * Not all arrays are wiggle-sortable. This algorithm detects non-wiggle-sortable inputs — for + * example [1, 2, 2], or arrays where more than half the values are equal — and throws an + * IllegalArgumentException instead of returning a wrongly ordered result. */ public class WiggleSort implements SortAlgorithm { diff --git a/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java b/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java index 98c439341a21..7afe2c99aae8 100644 --- a/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java +++ b/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java @@ -8,6 +8,13 @@ * which we will pop one-by-one to create the string in reverse. * * Reference: https://www.geeksforgeeks.org/check-whether-the-given-string-is-palindrome-using-stack/ + * + * @see com.thealgorithms.strings.Palindrome + * @see com.thealgorithms.bitmanipulation.BinaryPalindromeCheck + * @see com.thealgorithms.maths.LowestBasePalindrome + * @see com.thealgorithms.datastructures.lists.PalindromeSinglyLinkedList + * @see com.thealgorithms.maths.PalindromePrime + * @see com.thealgorithms.maths.PalindromeNumber */ public class PalindromeWithStack { private LinkedList stack; diff --git a/src/main/java/com/thealgorithms/strings/AlternativeStringArrange.java b/src/main/java/com/thealgorithms/strings/AlternativeStringArrange.java index cf736dbd8cab..016ee2821a17 100644 --- a/src/main/java/com/thealgorithms/strings/AlternativeStringArrange.java +++ b/src/main/java/com/thealgorithms/strings/AlternativeStringArrange.java @@ -21,12 +21,19 @@ private AlternativeStringArrange() { /** * Arranges two strings by alternating their characters. + * If one string is longer than the other, the remaining characters of the longer string + * are appended at the end of the result. * - * @param firstString the first input string - * @param secondString the second input string + * @param firstString the first input string, must not be {@code null} + * @param secondString the second input string, must not be {@code null} * @return a new string with characters from both strings arranged alternately + * @throws IllegalArgumentException if {@code firstString} or {@code secondString} is {@code null} */ public static String arrange(String firstString, String secondString) { + if (firstString == null || secondString == null) { + throw new IllegalArgumentException("Input strings must not be null"); + } + StringBuilder result = new StringBuilder(); int length1 = firstString.length(); int length2 = secondString.length(); diff --git a/src/main/java/com/thealgorithms/strings/LongestCommonSubstring.java b/src/main/java/com/thealgorithms/strings/LongestCommonSubstring.java new file mode 100644 index 000000000000..b2190316aff2 --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/LongestCommonSubstring.java @@ -0,0 +1,55 @@ +package com.thealgorithms.strings; + +/** + * Longest Common Substring finds the longest string that is a + * contiguous substring of two input strings. + * Example: "abcdef" and "zcdemf" -> "cde" + * + * @see + * Wikipedia: Longest Common Substring + * + * author: Vraj Prajapati @Rosander0 + */ +public final class LongestCommonSubstring { + + private LongestCommonSubstring() { + // Utility class + } + + /** + * Finds the longest common substring of two strings. + * + * @param a First input string + * @param b Second input string + * @return The longest common substring, or empty string if none exists. + * If multiple substrings share the maximum length, the first one found is returned. + */ + public static String longestCommonSubstring(final String a, final String b) { + if (a == null || b == null || a.isEmpty() || b.isEmpty()) { + return ""; + } + + int[][] dp = new int[a.length() + 1][b.length() + 1]; + int maxLength = 0; + int endIndex = 0; + + for (int i = 1; i <= a.length(); i++) { + for (int j = 1; j <= b.length(); j++) { + if (a.charAt(i - 1) == b.charAt(j - 1)) { + dp[i][j] = dp[i - 1][j - 1] + 1; + if (dp[i][j] > maxLength) { + maxLength = dp[i][j]; + endIndex = i; + } + } else { + dp[i][j] = 0; + } + } + } + + if (maxLength == 0) { + return ""; + } + return a.substring(endIndex - maxLength, endIndex); + } +} diff --git a/src/main/java/com/thealgorithms/strings/Palindrome.java b/src/main/java/com/thealgorithms/strings/Palindrome.java index 3567a371d70e..64de657df359 100644 --- a/src/main/java/com/thealgorithms/strings/Palindrome.java +++ b/src/main/java/com/thealgorithms/strings/Palindrome.java @@ -2,6 +2,13 @@ /** * Wikipedia: https://en.wikipedia.org/wiki/Palindrome + * + * @see com.thealgorithms.stacks.PalindromeWithStack + * @see com.thealgorithms.bitmanipulation.BinaryPalindromeCheck + * @see com.thealgorithms.maths.LowestBasePalindrome + * @see com.thealgorithms.datastructures.lists.PalindromeSinglyLinkedList + * @see com.thealgorithms.maths.PalindromePrime + * @see com.thealgorithms.maths.PalindromeNumber */ final class Palindrome { private Palindrome() { diff --git a/src/main/java/com/thealgorithms/strings/TitleCase.java b/src/main/java/com/thealgorithms/strings/TitleCase.java new file mode 100644 index 000000000000..eb6c73623681 --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/TitleCase.java @@ -0,0 +1,54 @@ +package com.thealgorithms.strings; + +/** + * Title Case converts a string so that the first letter of each word + * is capitalized and the rest are lowercase. + * Example: "the quick brown fox" -> "The Quick Brown Fox" + * + * @see + * Wikipedia: Title Case + */ +public final class TitleCase { + + private TitleCase() { + // Utility class + } + + /** + * Converts a string to title case. + * + * @param input The string to convert + * @return The title-cased string, or empty string if input is null/empty. + * If input contains only whitespace, it is returned as is. + */ + public static String toTitleCase(final String input) { + if (input == null || input.isEmpty()) { + return ""; + } + + StringBuilder result = new StringBuilder(input.length()); + boolean capitalizeNext = true; + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + if (Character.isWhitespace(c)) { + capitalizeNext = true; + result.append(c); + continue; + } + + if (capitalizeNext) { + if (Character.isLetter(c)) { + result.append(Character.toUpperCase(c)); + capitalizeNext = false; + } else { + // Keep capitalizeNext=true so the first *letter* after + // punctuation/digits is capitalized. + result.append(c); + } + } else { + result.append(Character.toLowerCase(c)); + } + } + return result.toString(); + } +} diff --git a/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java b/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java new file mode 100644 index 000000000000..ecd1f3c4dfae --- /dev/null +++ b/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java @@ -0,0 +1,99 @@ +package com.thealgorithms.backtracking; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.Test; + +class RatInAMazeTest { + + @Test + void testMultiplePathsExist() { + int[][] maze = {{1, 0, 0, 0}, {1, 1, 0, 1}, {0, 1, 0, 0}, {0, 1, 1, 1}}; + + List paths = RatInAMaze.findPaths(maze); + assertTrue(paths.size() >= 1); + for (String path : paths) { + assertTrue(path.chars().allMatch(c -> "DLRU".indexOf(c) >= 0)); + } + } + + @Test + void testSinglePath() { + int[][] maze = {{1, 0, 0}, {1, 1, 0}, {0, 1, 1}}; + List paths = RatInAMaze.findPaths(maze); + assertEquals(1, paths.size()); + assertEquals("DRDR", paths.get(0)); + } + + @Test + void testNoPathExists() { + int[][] maze = {{1, 0, 0}, {0, 0, 0}, {0, 0, 1}}; + List paths = RatInAMaze.findPaths(maze); + assertTrue(paths.isEmpty()); + } + + @Test + void testSourceBlocked() { + int[][] maze = {{0, 1}, {1, 1}}; + List paths = RatInAMaze.findPaths(maze); + assertTrue(paths.isEmpty()); + } + + @Test + void testDestinationBlocked() { + int[][] maze = {{1, 1}, {1, 0}}; + List paths = RatInAMaze.findPaths(maze); + assertTrue(paths.isEmpty()); + } + + @Test + void testSingleCellMazeOpen() { + int[][] maze = {{1}}; + List paths = RatInAMaze.findPaths(maze); + assertEquals(1, paths.size()); + assertEquals("", paths.get(0)); + } + + @Test + void testSingleCellMazeBlocked() { + int[][] maze = {{0}}; + List paths = RatInAMaze.findPaths(maze); + assertTrue(paths.isEmpty()); + } + + @Test + void testNullMazeThrowsException() { + assertThrows(IllegalArgumentException.class, () -> RatInAMaze.findPaths(null)); + } + + @Test + void testEmptyMazeThrowsException() { + assertThrows(IllegalArgumentException.class, () -> RatInAMaze.findPaths(new int[][] {})); + } + + @Test + void testNonSquareMazeThrowsException() { + int[][] maze = {{1, 0, 1}, {1, 1, 1}}; + assertThrows(IllegalArgumentException.class, () -> RatInAMaze.findPaths(maze)); + } + + @Test + void testAllCellsOpen() { + int[][] maze = {{1, 1, 1}, {1, 1, 1}, {1, 1, 1}}; + List paths = RatInAMaze.findPaths(maze); + assertTrue(paths.size() > 1); + } + + @Test + void testLargerMazeWithPath() { + int[][] maze = {{1, 1, 1, 1}, {0, 1, 0, 1}, {0, 1, 0, 1}, {0, 1, 1, 1}}; + List paths = RatInAMaze.findPaths(maze); + assertTrue(paths.size() >= 1); + for (String path : paths) { + assertTrue(path.chars().allMatch(c -> "DLRU".indexOf(c) >= 0), "Path contains invalid characters: " + path); + } + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/CaesarTest.java b/src/test/java/com/thealgorithms/ciphers/CaesarTest.java index 7aa41c4cf423..c8b20ad8d8f9 100644 --- a/src/test/java/com/thealgorithms/ciphers/CaesarTest.java +++ b/src/test/java/com/thealgorithms/ciphers/CaesarTest.java @@ -32,6 +32,29 @@ void caesarDecryptTest() { assertEquals("Encrypt this text", cipherText); } + @Test + void caesarEncryptWithNegativeShiftTest() { + // a shift of -1 must wrap 'A' backwards to 'Z', like a shift of +25 would + assertEquals("Z", caesar.encode("A", -1)); + assertEquals("z", caesar.encode("a", -1)); + assertEquals("EBIIL", caesar.encode("HELLO", -3)); + } + + @Test + void caesarDecryptWithNegativeShiftTest() { + assertEquals("A", caesar.decode("Z", -1)); + assertEquals("HELLO", caesar.decode("EBIIL", -3)); + } + + @Test + void caesarNegativeShiftRoundTripTest() { + // encode followed by decode with the same shift must return the original text + for (int shift : new int[] {-1, -5, -25, -26, -27, -52}) { + String message = "The quick brown Fox"; + assertEquals(message, caesar.decode(caesar.encode(message, shift), shift)); + } + } + @Test void caesarBruteForce() { // given diff --git a/src/test/java/com/thealgorithms/conversions/Base64Test.java b/src/test/java/com/thealgorithms/conversions/Base64Test.java index fbc220c0ca95..cd0d6c8b38a8 100644 --- a/src/test/java/com/thealgorithms/conversions/Base64Test.java +++ b/src/test/java/com/thealgorithms/conversions/Base64Test.java @@ -127,6 +127,9 @@ void testInvalidPaddingPosition() { assertThrows(IllegalArgumentException.class, () -> Base64.decode("Q=QQ")); assertThrows(IllegalArgumentException.class, () -> Base64.decode("Q=Q=")); assertThrows(IllegalArgumentException.class, () -> Base64.decode("=QQQ")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("QQ=Q")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("AB=C")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("AB=A")); } @Test diff --git a/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java b/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java index 8212793dfb79..f85a4628a1ac 100644 --- a/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java +++ b/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java @@ -98,11 +98,9 @@ void testIterator() { @Test void testIteratorEmptyBag() { Bag bag = new Bag<>(); - int count = 0; - for (String ignored : bag) { - org.junit.jupiter.api.Assertions.fail("Iterator should not return any items for an empty bag"); + for (String item : bag) { + org.junit.jupiter.api.Assertions.fail("Iterator returned item for an empty bag:" + item); } - assertEquals(0, count, "Iterator should not traverse any items in an empty bag"); } @Test diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithmTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithmTest.java deleted file mode 100644 index bf4e2828e069..000000000000 --- a/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithmTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.thealgorithms.datastructures.graphs; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class DijkstraOptimizedAlgorithmTest { - - private DijkstraOptimizedAlgorithm dijkstraOptimizedAlgorithm; - private int[][] graph; - - @BeforeEach - void setUp() { - graph = new int[][] { - {0, 4, 0, 0, 0, 0, 0, 8, 0}, - {4, 0, 8, 0, 0, 0, 0, 11, 0}, - {0, 8, 0, 7, 0, 4, 0, 0, 2}, - {0, 0, 7, 0, 9, 14, 0, 0, 0}, - {0, 0, 0, 9, 0, 10, 0, 0, 0}, - {0, 0, 4, 14, 10, 0, 2, 0, 0}, - {0, 0, 0, 0, 0, 2, 0, 1, 6}, - {8, 11, 0, 0, 0, 0, 1, 0, 7}, - {0, 0, 2, 0, 0, 0, 6, 7, 0}, - }; - - dijkstraOptimizedAlgorithm = new DijkstraOptimizedAlgorithm(graph.length); - } - - @Test - void testRunAlgorithm() { - int[] expectedDistances = {0, 4, 12, 19, 21, 11, 9, 8, 14}; - assertArrayEquals(expectedDistances, dijkstraOptimizedAlgorithm.run(graph, 0)); - } - - @Test - void testGraphWithDisconnectedNodes() { - int[][] disconnectedGraph = { - {0, 3, 0, 0}, {3, 0, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 0} // Node 3 is disconnected - }; - - DijkstraOptimizedAlgorithm dijkstraDisconnected = new DijkstraOptimizedAlgorithm(disconnectedGraph.length); - - // Testing from vertex 0 - int[] expectedDistances = {0, 3, 4, Integer.MAX_VALUE}; // Node 3 is unreachable - assertArrayEquals(expectedDistances, dijkstraDisconnected.run(disconnectedGraph, 0)); - } - - @Test - void testSingleVertexGraph() { - int[][] singleVertexGraph = {{0}}; - DijkstraOptimizedAlgorithm dijkstraSingleVertex = new DijkstraOptimizedAlgorithm(1); - - int[] expectedDistances = {0}; // The only vertex's distance to itself is 0 - assertArrayEquals(expectedDistances, dijkstraSingleVertex.run(singleVertexGraph, 0)); - } - - @Test - void testInvalidSourceVertex() { - assertThrows(IllegalArgumentException.class, () -> dijkstraOptimizedAlgorithm.run(graph, -1)); - assertThrows(IllegalArgumentException.class, () -> dijkstraOptimizedAlgorithm.run(graph, graph.length)); - } -} diff --git a/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java index 5483bbcd0394..4390c0f5f2eb 100644 --- a/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java @@ -2,98 +2,51 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; class MergeSortedArrayListTest { - @Test - void testMergeTwoSortedLists() { - List listA = Arrays.asList(1, 3, 5, 7, 9); - List listB = Arrays.asList(2, 4, 6, 8, 10); + @ParameterizedTest(name = "{3}") + @MethodSource("provideMergeTestData") + void testMergeParameterizedScenarios(List listA, List listB, List expected, String scenarioName) { List result = new ArrayList<>(); - MergeSortedArrayList.merge(listA, listB, result); - - List expected = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); - assertEquals(expected, result, "Merged list should be sorted and contain all elements from both input lists."); + assertEquals(expected, result, () -> "Failed scenario: " + scenarioName); } - @Test - void testMergeWithEmptyList() { - List listA = Arrays.asList(1, 2, 3); - List listB = new ArrayList<>(); // Empty list - List result = new ArrayList<>(); - - MergeSortedArrayList.merge(listA, listB, result); - - List expected = Arrays.asList(1, 2, 3); - assertEquals(expected, result, "Merged list should match listA when listB is empty."); - } - - @Test - void testMergeWithBothEmptyLists() { - List listA = new ArrayList<>(); // Empty list - List listB = new ArrayList<>(); // Empty list - List result = new ArrayList<>(); - - MergeSortedArrayList.merge(listA, listB, result); - - assertTrue(result.isEmpty(), "Merged list should be empty when both input lists are empty."); + private static Stream provideMergeTestData() { + return Stream.of(Arguments.of(Arrays.asList(1, 3, 5, 7, 9), Arrays.asList(2, 4, 6, 8, 10), Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), "Standard alternating sorted lists"), Arguments.of(Arrays.asList(1, 2, 3), new ArrayList<>(), Arrays.asList(1, 2, 3), "Merge with empty second list"), + Arguments.of(new ArrayList<>(), Arrays.asList(4, 5, 6), Arrays.asList(4, 5, 6), "Merge with empty first list"), Arguments.of(new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), "Merge with both lists empty"), + Arguments.of(Arrays.asList(1, 2, 2, 3), Arrays.asList(2, 3, 4), Arrays.asList(1, 2, 2, 2, 3, 3, 4), "Handling duplicate elements gracefully"), + Arguments.of(Arrays.asList(-3, -1, 2), Arrays.asList(-2, 0, 3), Arrays.asList(-3, -2, -1, 0, 2, 3), "Handling negative numbers mixed with positive numbers")); } @Test - void testMergeWithDuplicateElements() { - List listA = Arrays.asList(1, 2, 2, 3); - List listB = Arrays.asList(2, 3, 4); + void testMergeThrowsExceptionWhenListAIsNull() { + List listB = Arrays.asList(1, 2, 3); List result = new ArrayList<>(); - - MergeSortedArrayList.merge(listA, listB, result); - - List expected = Arrays.asList(1, 2, 2, 2, 3, 3, 4); - assertEquals(expected, result, "Merged list should correctly handle and include duplicate elements."); + assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(null, listB, result)); } @Test - void testMergeWithNegativeAndPositiveNumbers() { - List listA = Arrays.asList(-3, -1, 2); - List listB = Arrays.asList(-2, 0, 3); + void testMergeThrowsExceptionWhenListBIsNull() { + List listA = Arrays.asList(1, 2, 3); List result = new ArrayList<>(); - - MergeSortedArrayList.merge(listA, listB, result); - - List expected = Arrays.asList(-3, -2, -1, 0, 2, 3); - assertEquals(expected, result, "Merged list should correctly handle negative and positive numbers."); + assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(listA, null, result)); } @Test - void testMergeThrowsExceptionOnNullInput() { - List listA = null; - List listB = Arrays.asList(1, 2, 3); - List result = new ArrayList<>(); - - List finalListB = listB; - List finalListA = listA; - List finalResult = result; - assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(finalListA, finalListB, finalResult), "Should throw NullPointerException if any input list is null."); - - listA = Arrays.asList(1, 2, 3); - listB = null; - List finalListA1 = listA; - List finalListB1 = listB; - List finalResult1 = result; - assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(finalListA1, finalListB1, finalResult1), "Should throw NullPointerException if any input list is null."); - - listA = Arrays.asList(1, 2, 3); - listB = Arrays.asList(4, 5, 6); - result = null; - List finalListA2 = listA; - List finalListB2 = listB; - List finalResult2 = result; - assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(finalListA2, finalListB2, finalResult2), "Should throw NullPointerException if the result collection is null."); + void testMergeThrowsExceptionWhenResultCollectionIsNull() { + List listA = Arrays.asList(1, 2, 3); + List listB = Arrays.asList(4, 5, 6); + assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(listA, listB, null)); } } diff --git a/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedListTest.java similarity index 98% rename from src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java rename to src/test/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedListTest.java index 0f0577d39094..10f6b8536b19 100644 --- a/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedListTest.java @@ -1,9 +1,8 @@ -package com.thealgorithms.misc; +package com.thealgorithms.datastructures.lists; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.thealgorithms.datastructures.lists.SinglyLinkedList; import org.junit.jupiter.api.Test; public class PalindromeSinglyLinkedListTest { diff --git a/src/test/java/com/thealgorithms/datastructures/queues/ThreadSafeQueueTest.java b/src/test/java/com/thealgorithms/datastructures/queues/ThreadSafeQueueTest.java new file mode 100644 index 000000000000..4c038c05b167 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/queues/ThreadSafeQueueTest.java @@ -0,0 +1,295 @@ +package com.thealgorithms.datastructures.queues; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class ThreadSafeQueueTest { + + @Test + public void testEnqueueDequeue() throws InterruptedException { + ThreadSafeQueue queue = new ThreadSafeQueue<>(5); + queue.enqueue(1); + queue.enqueue(2); + queue.enqueue(3); + + Assertions.assertEquals(3, queue.size()); + Assertions.assertEquals(1, queue.dequeue()); + Assertions.assertEquals(2, queue.dequeue()); + Assertions.assertEquals(3, queue.dequeue()); + Assertions.assertTrue(queue.isEmpty()); + } + + @Test + public void testOfferPoll() { + ThreadSafeQueue queue = new ThreadSafeQueue<>(2); + Assertions.assertTrue(queue.offer("a")); + Assertions.assertTrue(queue.offer("b")); + Assertions.assertFalse(queue.offer("c")); + + Assertions.assertEquals("a", queue.poll()); + Assertions.assertEquals("b", queue.poll()); + Assertions.assertNull(queue.poll()); + } + + @Test + public void testOfferRejectsWhenFull() { + ThreadSafeQueue queue = new ThreadSafeQueue<>(2); + Assertions.assertTrue(queue.offer(1)); + Assertions.assertTrue(queue.offer(2)); + Assertions.assertFalse(queue.offer(3)); + Assertions.assertEquals(2, queue.size()); + } + + @Test + public void testPollReturnsNullWhenEmpty() { + ThreadSafeQueue queue = new ThreadSafeQueue<>(5); + Assertions.assertNull(queue.poll()); + } + + @Test + public void testEnqueueNullThrows() { + ThreadSafeQueue queue = new ThreadSafeQueue<>(5); + Assertions.assertThrows(IllegalArgumentException.class, () -> queue.enqueue(null)); + } + + @Test + public void testOfferNullThrows() { + ThreadSafeQueue queue = new ThreadSafeQueue<>(5); + Assertions.assertThrows(IllegalArgumentException.class, () -> queue.offer(null)); + } + + @Test + public void testInvalidCapacityThrows() { + Assertions.assertThrows(IllegalArgumentException.class, () -> new ThreadSafeQueue<>(0)); + Assertions.assertThrows(IllegalArgumentException.class, () -> new ThreadSafeQueue<>(-1)); + } + + @Test + public void testIsEmptyAndIsFull() throws InterruptedException { + ThreadSafeQueue queue = new ThreadSafeQueue<>(2); + Assertions.assertTrue(queue.isEmpty()); + Assertions.assertFalse(queue.isFull()); + + queue.enqueue(1); + Assertions.assertFalse(queue.isEmpty()); + Assertions.assertFalse(queue.isFull()); + + queue.enqueue(2); + Assertions.assertFalse(queue.isEmpty()); + Assertions.assertTrue(queue.isFull()); + + queue.dequeue(); + Assertions.assertFalse(queue.isEmpty()); + Assertions.assertFalse(queue.isFull()); + + queue.dequeue(); + Assertions.assertTrue(queue.isEmpty()); + Assertions.assertFalse(queue.isFull()); + } + + @Test + public void testCapacity() { + ThreadSafeQueue queue = new ThreadSafeQueue<>(10); + Assertions.assertEquals(10, queue.capacity()); + } + + @Test + public void testCircularBufferWrapAround() throws InterruptedException { + ThreadSafeQueue queue = new ThreadSafeQueue<>(3); + queue.enqueue(1); + queue.enqueue(2); + queue.enqueue(3); + + Assertions.assertEquals(1, queue.dequeue()); + Assertions.assertEquals(2, queue.dequeue()); + + queue.enqueue(4); + queue.enqueue(5); + + Assertions.assertEquals(3, queue.dequeue()); + Assertions.assertEquals(4, queue.dequeue()); + Assertions.assertEquals(5, queue.dequeue()); + } + + @Test + public void testMultipleProducersSingleConsumer() throws InterruptedException { + ThreadSafeQueue queue = new ThreadSafeQueue<>(100); + int numProducers = 4; + int itemsPerProducer = 250; + int totalItems = numProducers * itemsPerProducer; + CountDownLatch doneLatch = new CountDownLatch(numProducers); + List results = new ArrayList<>(); + + ExecutorService executor = Executors.newFixedThreadPool(numProducers + 1); + + for (int p = 0; p < numProducers; p++) { + final int producerId = p; + executor.submit(() -> { + try { + for (int i = 0; i < itemsPerProducer; i++) { + queue.enqueue(producerId * itemsPerProducer + i); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + doneLatch.countDown(); + } + }); + } + + Thread consumerThread = new Thread(() -> { + try { + while (results.size() < totalItems) { + Integer item = queue.poll(); + if (item != null) { + synchronized (results) { + results.add(item); + } + } + } + } catch (Exception e) { + Thread.currentThread().interrupt(); + } + }); + consumerThread.start(); + + Assertions.assertTrue(doneLatch.await(10, TimeUnit.SECONDS)); + consumerThread.join(5000); + + Assertions.assertEquals(totalItems, results.size()); + executor.shutdown(); + Assertions.assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS)); + } + + @Test + public void testSingleProducerMultipleConsumers() throws InterruptedException { + ThreadSafeQueue queue = new ThreadSafeQueue<>(50); + int numConsumers = 4; + int totalItems = 1000; + CountDownLatch doneLatch = new CountDownLatch(numConsumers); + AtomicInteger consumedCount = new AtomicInteger(0); + + ExecutorService executor = Executors.newFixedThreadPool(numConsumers + 1); + + executor.submit(() -> { + try { + for (int i = 0; i < totalItems; i++) { + queue.enqueue(i); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + + for (int c = 0; c < numConsumers; c++) { + executor.submit(() -> { + try { + while (consumedCount.get() < totalItems) { + Integer item = queue.poll(); + if (item != null) { + consumedCount.incrementAndGet(); + } + } + } finally { + doneLatch.countDown(); + } + }); + } + + Assertions.assertTrue(doneLatch.await(10, TimeUnit.SECONDS)); + Assertions.assertEquals(totalItems, consumedCount.get()); + executor.shutdown(); + Assertions.assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS)); + } + + @Test + public void testBlockingEnqueueWhenFull() throws InterruptedException { + ThreadSafeQueue queue = new ThreadSafeQueue<>(1); + queue.enqueue(1); + + AtomicInteger blockedCount = new AtomicInteger(0); + Thread producer = new Thread(() -> { + try { + queue.enqueue(2); + blockedCount.incrementAndGet(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + producer.start(); + + Thread.sleep(100); + Assertions.assertEquals(1, queue.dequeue()); + + producer.join(2000); + Assertions.assertEquals(1, blockedCount.get()); + Assertions.assertEquals(2, queue.dequeue()); + } + + @Test + public void testBlockingDequeueWhenEmpty() throws InterruptedException { + ThreadSafeQueue queue = new ThreadSafeQueue<>(5); + + AtomicInteger result = new AtomicInteger(-1); + Thread consumer = new Thread(() -> { + try { + result.set(queue.dequeue()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + consumer.start(); + + Thread.sleep(100); + queue.enqueue(42); + + consumer.join(2000); + Assertions.assertEquals(42, result.get()); + } + + @Test + public void testStressConcurrentAccess() throws InterruptedException { + ThreadSafeQueue queue = new ThreadSafeQueue<>(10); + int numThreads = 8; + int opsPerThread = 500; + CountDownLatch latch = new CountDownLatch(numThreads); + AtomicInteger enqueueCount = new AtomicInteger(0); + AtomicInteger dequeueCount = new AtomicInteger(0); + + ExecutorService executor = Executors.newFixedThreadPool(numThreads); + + for (int t = 0; t < numThreads; t++) { + final boolean isProducer = t % 2 == 0; + executor.submit(() -> { + try { + for (int i = 0; i < opsPerThread; i++) { + if (isProducer) { + if (queue.offer(i)) { + enqueueCount.incrementAndGet(); + } + } else { + if (queue.poll() != null) { + dequeueCount.incrementAndGet(); + } + } + } + } finally { + latch.countDown(); + } + }); + } + + Assertions.assertTrue(latch.await(10, TimeUnit.SECONDS)); + Assertions.assertTrue(enqueueCount.get() >= dequeueCount.get()); + Assertions.assertEquals(enqueueCount.get() - dequeueCount.get(), queue.size()); + executor.shutdown(); + executor.awaitTermination(5, TimeUnit.SECONDS); + } +} diff --git a/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java b/src/test/java/com/thealgorithms/datastructures/trees/HeavyLightDecompositionTest.java similarity index 97% rename from src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java rename to src/test/java/com/thealgorithms/datastructures/trees/HeavyLightDecompositionTest.java index 29189290e1d4..f0cb1724f67c 100644 --- a/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java +++ b/src/test/java/com/thealgorithms/datastructures/trees/HeavyLightDecompositionTest.java @@ -1,4 +1,4 @@ -package com.thealgorithms.tree; +package com.thealgorithms.datastructures.trees; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/src/test/java/com/thealgorithms/datastructures/trees/WaveletTreeTest.java b/src/test/java/com/thealgorithms/datastructures/trees/WaveletTreeTest.java new file mode 100644 index 000000000000..592170673a3a --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/trees/WaveletTreeTest.java @@ -0,0 +1,117 @@ +package com.thealgorithms.datastructures.trees; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class WaveletTreeTest { + + @Test + public void testRank() { + int[] arr = {5, 1, 2, 5, 1}; + WaveletTree wt = new WaveletTree(arr); + + // x = 1 + assertEquals(1, wt.rank(1, 1)); // In [5, 1], '1' appears 1 time + assertEquals(2, wt.rank(1, 4)); // In [5, 1, 2, 5, 1], '1' appears 2 times + assertEquals(0, wt.rank(1, 0)); // In [5], '1' appears 0 times + + // x = 5 + assertEquals(1, wt.rank(5, 0)); // In [5], '5' appears 1 time + assertEquals(1, wt.rank(5, 2)); // In [5, 1, 2], '5' appears 1 time + assertEquals(2, wt.rank(5, 4)); // In [5, 1, 2, 5, 1], '5' appears 2 times + + // Out of bounds / invalid value + assertEquals(0, wt.rank(10, 4)); // '10' is not in the array + assertEquals(0, wt.rank(5, -1)); // Invalid end index + } + + @Test + public void testSelect() { + int[] arr = {5, 1, 2, 5, 1}; + WaveletTree wt = new WaveletTree(arr); + + assertEquals(1, wt.select(1, 1)); // 1st '1' is at index 1 + assertEquals(4, wt.select(1, 2)); // 2nd '1' is at index 4 + + assertEquals(0, wt.select(5, 1)); // 1st '5' is at index 0 + assertEquals(3, wt.select(5, 2)); // 2nd '5' is at index 3 + + assertEquals(2, wt.select(2, 1)); // 1st '2' is at index 2 + + assertEquals(-1, wt.select(5, 3)); // 3rd '5' doesn't exist + assertEquals(-1, wt.select(10, 1)); // '10' doesn't exist + assertEquals(-1, wt.select(5, 0)); // invalid k + } + + @Test + public void testKthSmallest() { + int[] arr = {5, 1, 2, 5, 1}; + WaveletTree wt = new WaveletTree(arr); + + // Array: [5, 1, 2, 5, 1] -> Sorted: [1, 1, 2, 5, 5] + assertEquals(1, wt.kthSmallest(0, 4, 1)); // 1st smallest in [5, 1, 2, 5, 1] is 1 + assertEquals(1, wt.kthSmallest(0, 4, 2)); // 2nd smallest in [5, 1, 2, 5, 1] is 1 + assertEquals(2, wt.kthSmallest(0, 4, 3)); // 3rd smallest in [5, 1, 2, 5, 1] is 2 + assertEquals(5, wt.kthSmallest(0, 4, 4)); // 4th smallest in [5, 1, 2, 5, 1] is 5 + assertEquals(5, wt.kthSmallest(0, 4, 5)); // 5th smallest in [5, 1, 2, 5, 1] is 5 + + // Subarray: arr[1..3] = [1, 2, 5] -> Sorted: [1, 2, 5] + assertEquals(1, wt.kthSmallest(1, 3, 1)); // 1st smallest in [1, 2, 5] is 1 + assertEquals(2, wt.kthSmallest(1, 3, 2)); // 2nd smallest in [1, 2, 5] is 2 + assertEquals(5, wt.kthSmallest(1, 3, 3)); // 3rd smallest in [1, 2, 5] is 5 + + // Invalid ranges / arguments + assertEquals(-1, wt.kthSmallest(4, 2, 1)); // Invalid range (left > right) + assertEquals(-1, wt.kthSmallest(0, 4, 10)); // k > range length + assertEquals(-1, wt.kthSmallest(0, 4, 0)); // k < 1 + } + + @Test + public void testEmptyAndSingleElementArray() { + WaveletTree wtEmpty = new WaveletTree(new int[] {}); + assertEquals(0, wtEmpty.rank(1, 0)); + assertEquals(-1, wtEmpty.select(1, 1)); + assertEquals(-1, wtEmpty.kthSmallest(0, 0, 1)); + + WaveletTree wtSingle = new WaveletTree(new int[] {42}); + assertEquals(1, wtSingle.rank(42, 0)); + assertEquals(0, wtSingle.rank(42, -1)); + assertEquals(0, wtSingle.select(42, 1)); + assertEquals(-1, wtSingle.select(42, 2)); + assertEquals(42, wtSingle.kthSmallest(0, 0, 1)); + } + + @Test + public void testNullArrayAndCustomBounds() { + WaveletTree wtNull = new WaveletTree(null); + assertEquals(0, wtNull.rank(1, 0)); + + WaveletTree wtNullCustom = new WaveletTree(null, 1, 5); + assertEquals(-1, wtNullCustom.select(1, 1)); + + int[] arr = {5, 1, 2, 5, 1}; + WaveletTree wtCustom = new WaveletTree(arr, 1, 10); + assertEquals(2, wtCustom.rank(5, 4)); + assertEquals(0, wtCustom.rank(4, 4)); // Query an element inside bounds but not in array + assertEquals(0, wtCustom.rank(10, 4)); // Query upper bound + } + + @Test + public void testNegativeValues() { + int[] arr = {-5, 10, -2, 0, -5}; + WaveletTree wt = new WaveletTree(arr); + + assertEquals(2, wt.rank(-5, 4)); + assertEquals(1, wt.rank(0, 3)); + + assertEquals(0, wt.select(-5, 1)); + assertEquals(4, wt.select(-5, 2)); + assertEquals(3, wt.select(0, 1)); + + // Sorted: [-5, -5, -2, 0, 10] + assertEquals(-5, wt.kthSmallest(0, 4, 1)); + assertEquals(-2, wt.kthSmallest(0, 4, 3)); + assertEquals(10, wt.kthSmallest(0, 4, 5)); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/DigitDPTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/DigitDPTest.java new file mode 100644 index 000000000000..762fe86d4d65 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/DigitDPTest.java @@ -0,0 +1,70 @@ +package com.thealgorithms.dynamicprogramming; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the generalized DigitDP implementation. + */ +public class DigitDPTest { + + @Test + public void testDigitDPBasicRange() { + // Numbers between 1 and 20 with a digit sum of 5: 5, 14 + long result = DigitDP.countRangeWithDigitSum(1, 20, 5); + assertEquals(2, result); + } + + @Test + public void testDigitDPZeroBound() { + // Number 0 has a digit sum of 0 + long result = DigitDP.countRangeWithDigitSum(0, 0, 0); + assertEquals(1, result); + } + + @Test + public void testDigitDPLargeRange() { + // Count numbers between 1 and 100 with a digit sum of 9 + // 9, 18, 27, 36, 45, 54, 63, 72, 81, 90 (10 numbers) + long result = DigitDP.countRangeWithDigitSum(1, 100, 9); + assertEquals(10, result); + } + + @Test + public void testDigitDPNoMatches() { + // No numbers between 10 and 15 can have a digit sum of 20 + long result = DigitDP.countRangeWithDigitSum(10, 15, 20); + assertEquals(0, result); + } + + @Test + public void testDigitDPExceedsMaxSum() { + // Sum condition that exceeds max possible physical sum array constraints + // gracefully returns 0 + long result = DigitDP.countRangeWithDigitSum(1, 100, 200); + assertEquals(0, result); + } + + @Test + public void testDigitDPInvalidRange() { + // Lower bound greater than upper bound should evaluate gracefully to 0 + long result = DigitDP.countRangeWithDigitSum(50, 20, 5); + assertEquals(0, result); + } + + @Test + public void testDigitDPExceedsMaxSumEdgeCase() { + // Yeh test case target > MAX_DIGIT_SUM wali condition ko hit karega + long result = DigitDP.countRangeWithDigitSum(1, 100, 180); + assertEquals(0, result); + } + + @Test + public void testDigitDPMemoizationHit() { + // Badi range dene se overlapping subproblems bante hain, + // jisse memoization hit hogi aur coverage 100% ho jayegi. + long result1 = DigitDP.countRangeWithDigitSum(1, 100000, 15); + long result2 = DigitDP.countRangeWithDigitSum(1, 100000, 15); + assertEquals(result1, result2); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequenceTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequenceTest.java new file mode 100644 index 000000000000..a1ee624e94d2 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequenceTest.java @@ -0,0 +1,54 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +public class LongestPalindromicSubsequenceTest { + + @ParameterizedTest + @CsvSource({"BBABCBCAB, BACBCAB", "BABCBAB, BABCBAB", "A, A", "AA, AA", "AB, B"}) + void testLpsKnownCases(String input, String expectedLps) { + assertEquals(expectedLps, LongestPalindromicSubsequence.lps(input)); + } + + @Test + void testLpsEmptyString() { + assertEquals("", LongestPalindromicSubsequence.lps("")); + } + + @Test + void testLpsSingleCharacter() { + assertEquals("Z", LongestPalindromicSubsequence.lps("Z")); + } + + @Test + void testLpsAllSameCharacters() { + assertEquals("AAAA", LongestPalindromicSubsequence.lps("AAAA")); + } + + @Test + void testLpsAlreadyPalindrome() { + assertEquals("RACECAR", LongestPalindromicSubsequence.lps("RACECAR")); + } + + @Test + void testLpsNoRepeatingCharacters() { + assertEquals(1, LongestPalindromicSubsequence.lps("ABCDE").length()); + } + + @Test + void testLpsNullThrowsException() { + assertThrows(IllegalArgumentException.class, () -> { LongestPalindromicSubsequence.lps(null); }); + } + + @Test + void testLpsResultIsActuallyPalindrome() { + String result = LongestPalindromicSubsequence.lps("BBABCBCAB"); + String reversed = new StringBuilder(result).reverse().toString(); + assertEquals(result, reversed); + } +} diff --git a/src/test/java/com/thealgorithms/machinelearning/LinearRegressionTest.java b/src/test/java/com/thealgorithms/machinelearning/LinearRegressionTest.java new file mode 100644 index 000000000000..e8a130d0ccb2 --- /dev/null +++ b/src/test/java/com/thealgorithms/machinelearning/LinearRegressionTest.java @@ -0,0 +1,95 @@ +package com.thealgorithms.machinelearning; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class LinearRegressionTest { + + private static final double DELTA = 0.1; + + @Test + void fitLearnsCorrectSlopeAndIntercept() { + double trueM = 2.5; + double trueB = 1.5; + + double[] xTrain = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; + double[] yTrain = new double[xTrain.length]; + for (int i = 0; i < xTrain.length; i++) { + yTrain[i] = (trueM * xTrain[i]) + trueB; + } + + LinearRegression model = new LinearRegression(0.01, 1000); + model.fit(xTrain, yTrain); + + assertEquals(trueM, model.getSlope(), DELTA); + assertEquals(trueB, model.getIntercept(), DELTA); + } + + @Test + void predictMatchesExpectedOnUnseenData() { + double trueM = 2.5; + double trueB = 1.5; + + double[] xTrain = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; + double[] yTrain = new double[xTrain.length]; + for (int i = 0; i < xTrain.length; i++) { + yTrain[i] = (trueM * xTrain[i]) + trueB; + } + + LinearRegression model = new LinearRegression(0.01, 1000); + model.fit(xTrain, yTrain); + + double[] testInputs = {0.0, 3.5, 7.0}; + for (double testX : testInputs) { + double expectedY = (trueM * testX) + trueB; + assertEquals(expectedY, model.predict(testX), DELTA); + } + } + + @Test + void calculateMSEIsNearZeroAfterTraining() { + double[] xTrain = {1.0, 2.0, 3.0, 4.0, 5.0}; + double[] yTrain = {3.0, 5.0, 7.0, 9.0, 11.0}; // y = 2x + 1 + + LinearRegression model = new LinearRegression(0.01, 1000); + model.fit(xTrain, yTrain); + + assertEquals(0.0, model.calculateMSE(xTrain, yTrain), 0.01); + } + + @Test + void fitThrowsExceptionOnMismatchedArrayLengths() { + LinearRegression model = new LinearRegression(0.01, 100); + double[] x = {1.0, 2.0}; + double[] y = {1.0}; + + assertThrows(IllegalArgumentException.class, () -> model.fit(x, y)); + } + + @Test + void fitThrowsExceptionOnEmptyArrays() { + LinearRegression model = new LinearRegression(0.01, 100); + double[] x = {}; + double[] y = {}; + + assertThrows(IllegalArgumentException.class, () -> model.fit(x, y)); + } + + @Test + void fitThrowsExceptionOnNullX() { + LinearRegression model = new LinearRegression(0.01, 100); + double[] y = {1.0, 2.0}; + + assertThrows(IllegalArgumentException.class, () -> model.fit(null, y)); + } + + @Test + void fitThrowsExceptionOnNullY() { + LinearRegression model = new LinearRegression(0.01, 100); + double[] x = {1.0, 2.0}; + + assertThrows(IllegalArgumentException.class, () -> model.fit(x, null)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/AbsoluteMinTest.java b/src/test/java/com/thealgorithms/maths/AbsoluteMinTest.java index dfca757fd877..070ff4ae3147 100644 --- a/src/test/java/com/thealgorithms/maths/AbsoluteMinTest.java +++ b/src/test/java/com/thealgorithms/maths/AbsoluteMinTest.java @@ -11,12 +11,15 @@ public class AbsoluteMinTest { void testGetMinValue() { assertEquals(0, AbsoluteMin.getMinValue(4, 0, 16)); assertEquals(-2, AbsoluteMin.getMinValue(3, -10, -2)); + assertEquals(-2, AbsoluteMin.getMinValue(-3, -10, -2)); + assertEquals(2, AbsoluteMin.getMinValue(-3, -10, 2)); + assertEquals(2, AbsoluteMin.getMinValue(-5, 2)); + assertEquals(2, AbsoluteMin.getMinValue(2, -5)); } @Test void testGetMinValueWithNoArguments() { - Exception exception = assertThrows(IllegalArgumentException.class, AbsoluteMin::getMinValue); - assertEquals("Numbers array cannot be empty", exception.getMessage()); + assertThrows(IllegalArgumentException.class, AbsoluteMin::getMinValue); } @Test @@ -24,4 +27,13 @@ void testGetMinValueWithSameAbsoluteValues() { assertEquals(-5, AbsoluteMin.getMinValue(-5, 5)); assertEquals(-5, AbsoluteMin.getMinValue(5, -5)); } + + @Test + void testIntegerMinValueOverflow() { + assertEquals(1, AbsoluteMin.getMinValue(Integer.MIN_VALUE, 1)); + assertEquals(-1, AbsoluteMin.getMinValue(Integer.MIN_VALUE, -1)); + assertEquals(0, AbsoluteMin.getMinValue(Integer.MIN_VALUE, 0)); + assertEquals(Integer.MIN_VALUE, AbsoluteMin.getMinValue(Integer.MIN_VALUE)); + assertEquals(Integer.MAX_VALUE, AbsoluteMin.getMinValue(Integer.MIN_VALUE, Integer.MAX_VALUE)); + } } diff --git a/src/test/java/com/thealgorithms/maths/DisariumNumberTest.java b/src/test/java/com/thealgorithms/maths/DisariumNumberTest.java new file mode 100644 index 000000000000..54e9e861ad44 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/DisariumNumberTest.java @@ -0,0 +1,32 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class DisariumNumberTest { + + @Test + void testDisariumNumbers() { + assertTrue(DisariumNumber.isDisarium(1)); + assertTrue(DisariumNumber.isDisarium(89)); + assertTrue(DisariumNumber.isDisarium(135)); + assertTrue(DisariumNumber.isDisarium(175)); + assertTrue(DisariumNumber.isDisarium(518)); + } + + @Test + void testNonDisariumNumbers() { + assertFalse(DisariumNumber.isDisarium(10)); + assertFalse(DisariumNumber.isDisarium(100)); + assertFalse(DisariumNumber.isDisarium(200)); + } + + @Test + void testInvalidInput() { + assertThrows(IllegalArgumentException.class, () -> DisariumNumber.isDisarium(0)); + assertThrows(IllegalArgumentException.class, () -> DisariumNumber.isDisarium(-5)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/FriendlyNumberTest.java b/src/test/java/com/thealgorithms/maths/FriendlyNumberTest.java new file mode 100644 index 000000000000..be5ddd7ee79e --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/FriendlyNumberTest.java @@ -0,0 +1,33 @@ +package com.thealgorithms.maths; +// author: Vraj Prajapati @Rosander0 + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class FriendlyNumberTest { + + @Test + public void testFriendlyNumbers() { + // 6 and 28 are friendly (abundancy index = 2) + assertTrue(FriendlyNumber.areFriendly(6, 28)); + // Every number is friendly with itself + assertTrue(FriendlyNumber.areFriendly(6, 6)); + assertTrue(FriendlyNumber.areFriendly(1, 1)); + } + + @Test + public void testNonFriendlyNumbers() { + assertFalse(FriendlyNumber.areFriendly(6, 10)); + assertFalse(FriendlyNumber.areFriendly(10, 15)); + assertFalse(FriendlyNumber.areFriendly(4, 9)); + } + + @Test + public void testInvalidInputs() { + assertFalse(FriendlyNumber.areFriendly(0, 6)); + assertFalse(FriendlyNumber.areFriendly(-1, 6)); + assertFalse(FriendlyNumber.areFriendly(6, -1)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/JacobsthalNumberTest.java b/src/test/java/com/thealgorithms/maths/JacobsthalNumberTest.java new file mode 100644 index 000000000000..19558510f916 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/JacobsthalNumberTest.java @@ -0,0 +1,34 @@ +package com.thealgorithms.maths; +// author: Vraj Prajapati @Rosander0 + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class JacobsthalNumberTest { + + @Test + public void testBaseCases() { + assertEquals(0, JacobsthalNumber.jacobsthal(0)); + assertEquals(1, JacobsthalNumber.jacobsthal(1)); + } + + @Test + public void testKnownValues() { + assertEquals(1, JacobsthalNumber.jacobsthal(2)); + assertEquals(3, JacobsthalNumber.jacobsthal(3)); + assertEquals(5, JacobsthalNumber.jacobsthal(4)); + assertEquals(11, JacobsthalNumber.jacobsthal(5)); + assertEquals(21, JacobsthalNumber.jacobsthal(6)); + assertEquals(43, JacobsthalNumber.jacobsthal(7)); + assertEquals(85, JacobsthalNumber.jacobsthal(8)); + assertEquals(171, JacobsthalNumber.jacobsthal(9)); + assertEquals(341, JacobsthalNumber.jacobsthal(10)); + } + + @Test + public void testInvalidInput() { + assertThrows(IllegalArgumentException.class, () -> JacobsthalNumber.jacobsthal(-1)); + } +} diff --git a/src/test/java/com/thealgorithms/others/LowestBasePalindromeTest.java b/src/test/java/com/thealgorithms/maths/LowestBasePalindromeTest.java similarity index 99% rename from src/test/java/com/thealgorithms/others/LowestBasePalindromeTest.java rename to src/test/java/com/thealgorithms/maths/LowestBasePalindromeTest.java index 7c3ce6635aa0..5a3d1c64b379 100644 --- a/src/test/java/com/thealgorithms/others/LowestBasePalindromeTest.java +++ b/src/test/java/com/thealgorithms/maths/LowestBasePalindromeTest.java @@ -1,4 +1,4 @@ -package com.thealgorithms.others; +package com.thealgorithms.maths; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/test/java/com/thealgorithms/maths/PadovanSequenceTest.java b/src/test/java/com/thealgorithms/maths/PadovanSequenceTest.java new file mode 100644 index 000000000000..b9d7f04b4d0b --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/PadovanSequenceTest.java @@ -0,0 +1,36 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** + * @author Vraj Prajapati (@Rosander0) + */ +public class PadovanSequenceTest { + + @Test + public void testBaseCase() { + assertEquals(1, PadovanSequence.padovan(0)); + assertEquals(1, PadovanSequence.padovan(1)); + assertEquals(1, PadovanSequence.padovan(2)); + } + + @Test + public void testKnownValues() { + assertEquals(2, PadovanSequence.padovan(3)); + assertEquals(2, PadovanSequence.padovan(4)); + assertEquals(3, PadovanSequence.padovan(5)); + assertEquals(4, PadovanSequence.padovan(6)); + assertEquals(5, PadovanSequence.padovan(7)); + assertEquals(7, PadovanSequence.padovan(8)); + assertEquals(9, PadovanSequence.padovan(9)); + assertEquals(12, PadovanSequence.padovan(10)); + } + + @Test + public void testInvalidInput() { + assertThrows(IllegalArgumentException.class, () -> PadovanSequence.padovan(-1)); + } +} diff --git a/src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java b/src/test/java/com/thealgorithms/maths/PalindromePrimeTest.java similarity index 98% rename from src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java rename to src/test/java/com/thealgorithms/maths/PalindromePrimeTest.java index 130cd19b47b1..2405da558700 100644 --- a/src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java +++ b/src/test/java/com/thealgorithms/maths/PalindromePrimeTest.java @@ -1,4 +1,4 @@ -package com.thealgorithms.misc; +package com.thealgorithms.maths; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/src/test/java/com/thealgorithms/maths/PerrinNumberTest.java b/src/test/java/com/thealgorithms/maths/PerrinNumberTest.java new file mode 100644 index 000000000000..0ec476dc0641 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/PerrinNumberTest.java @@ -0,0 +1,34 @@ +package com.thealgorithms.maths; +// author: Vraj Prajapati @Rosander0 + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class PerrinNumberTest { + + @Test + public void testBaseCases() { + assertEquals(3, PerrinNumber.perrin(0)); + assertEquals(0, PerrinNumber.perrin(1)); + assertEquals(2, PerrinNumber.perrin(2)); + } + + @Test + public void testKnownValues() { + assertEquals(3, PerrinNumber.perrin(3)); + assertEquals(2, PerrinNumber.perrin(4)); + assertEquals(5, PerrinNumber.perrin(5)); + assertEquals(5, PerrinNumber.perrin(6)); + assertEquals(7, PerrinNumber.perrin(7)); + assertEquals(10, PerrinNumber.perrin(8)); + assertEquals(12, PerrinNumber.perrin(9)); + assertEquals(17, PerrinNumber.perrin(10)); + } + + @Test + public void testInvalidInput() { + assertThrows(IllegalArgumentException.class, () -> PerrinNumber.perrin(-1)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/SociableNumberTest.java b/src/test/java/com/thealgorithms/maths/SociableNumberTest.java new file mode 100644 index 000000000000..b4877cd761a7 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/SociableNumberTest.java @@ -0,0 +1,56 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link SociableNumber}. + * + * @author Vraj Prajapati (@Rosander0) + */ +public class SociableNumberTest { + + @Test + public void testSumOfProperDivisorsEdgeCases() { + assertEquals(0, SociableNumber.sumOfProperDivisors(0)); + assertEquals(0, SociableNumber.sumOfProperDivisors(-5)); + assertEquals(0, SociableNumber.sumOfProperDivisors(1)); + assertEquals(1, SociableNumber.sumOfProperDivisors(2)); + } + + @Test + public void testSociableCycleOfLengthFive() { + assertTrue(SociableNumber.isSociable(12496, 5)); + } + + @Test + public void testAmicableNumbersAreSociableOfLengthTwo() { + assertTrue(SociableNumber.isSociable(220, 2)); + assertTrue(SociableNumber.isSociable(284, 2)); + } + + @Test + public void testNonSociableNumbers() { + assertFalse(SociableNumber.isSociable(12, 5)); + assertFalse(SociableNumber.isSociable(10, 3)); + } + + @Test + public void testEarlyCycleReturn() { + // 220 has cycle length 2; requesting a different length should return + // false because it returns to the start too early. + assertFalse(SociableNumber.isSociable(220, 3)); + assertFalse(SociableNumber.isSociable(284, 4)); + assertFalse(SociableNumber.isSociable(12496, 3)); + } + + @Test + public void testInvalidInputs() { + assertFalse(SociableNumber.isSociable(0, 5)); + assertFalse(SociableNumber.isSociable(-1, 5)); + assertFalse(SociableNumber.isSociable(220, 1)); + } +} diff --git a/src/test/java/com/thealgorithms/matrix/QRDecompositionTest.java b/src/test/java/com/thealgorithms/matrix/QRDecompositionTest.java new file mode 100644 index 000000000000..adc8fb717e57 --- /dev/null +++ b/src/test/java/com/thealgorithms/matrix/QRDecompositionTest.java @@ -0,0 +1,115 @@ +package com.thealgorithms.matrix; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class QRDecompositionTest { + + private static final double DELTA = 1e-9; + + @Test + public void testQRDecomposition2x2() { + double[][] matrix = {{12, -51}, {6, 167}}; + QRDecomposition.QR qr = QRDecomposition.decompose(matrix); + double[][] q = qr.getQ(); + double[][] r = qr.getR(); + + double[][] reconstructed = multiplyMatrices(q, r); + for (int i = 0; i < matrix.length; i++) { + assertArrayEquals(matrix[i], reconstructed[i], DELTA); + } + } + + @Test + public void testQRDecomposition3x3() { + double[][] matrix = {{1, 1, 0}, {1, 0, 1}, {0, 1, 1}}; + QRDecomposition.QR qr = QRDecomposition.decompose(matrix); + double[][] q = qr.getQ(); + double[][] r = qr.getR(); + + double[][] reconstructed = multiplyMatrices(q, r); + for (int i = 0; i < matrix.length; i++) { + assertArrayEquals(matrix[i], reconstructed[i], DELTA); + } + } + + @Test + public void testQROrthogonalColumns() { + double[][] matrix = {{1, 1, 0}, {1, 0, 1}, {0, 1, 1}}; + QRDecomposition.QR qr = QRDecomposition.decompose(matrix); + double[][] q = qr.getQ(); + + for (int i = 0; i < q[0].length; i++) { + for (int j = i; j < q[0].length; j++) { + double dot = 0; + for (int k = 0; k < q.length; k++) { + dot += q[k][i] * q[k][j]; + } + if (i == j) { + assertArrayEquals(new double[] {1.0}, new double[] {dot}, DELTA); + } else { + assertArrayEquals(new double[] {0.0}, new double[] {dot}, DELTA); + } + } + } + } + + @Test + public void testRIsUpperTriangular() { + double[][] matrix = {{12, -51}, {6, 167}}; + QRDecomposition.QR qr = QRDecomposition.decompose(matrix); + double[][] r = qr.getR(); + + for (int i = 1; i < r.length; i++) { + for (int j = 0; j < i; j++) { + assertArrayEquals(new double[] {0.0}, new double[] {r[i][j]}, DELTA); + } + } + } + + @Test + public void testQRDecompositionIdentityMatrix() { + double[][] matrix = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; + QRDecomposition.QR qr = QRDecomposition.decompose(matrix); + double[][] q = qr.getQ(); + double[][] r = qr.getR(); + + for (int i = 0; i < matrix.length; i++) { + assertArrayEquals(matrix[i], q[i], DELTA); + assertArrayEquals(matrix[i], r[i], DELTA); + } + } + + @Test + public void testQRDecompositionRankDeficientThrows() { + double[][] matrix = {{1, 2}, {2, 4}}; + assertThrows(ArithmeticException.class, () -> QRDecomposition.decompose(matrix)); + } + + @Test + public void testQRDecompositionNullMatrixThrows() { + assertThrows(IllegalArgumentException.class, () -> QRDecomposition.decompose(null)); + } + + @Test + public void testQRDecompositionEmptyMatrixThrows() { + assertThrows(IllegalArgumentException.class, () -> QRDecomposition.decompose(new double[0][0])); + } + + private static double[][] multiplyMatrices(double[][] a, double[][] b) { + int m = a.length; + int n = b[0].length; + int k = a[0].length; + double[][] result = new double[m][n]; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + for (int p = 0; p < k; p++) { + result[i][j] += a[i][p] * b[p][j]; + } + } + } + return result; + } +} diff --git a/src/test/java/com/thealgorithms/searches/InterpolationSearchTest.java b/src/test/java/com/thealgorithms/searches/InterpolationSearchTest.java index b3b7e7ef129c..b7ef64125da8 100644 --- a/src/test/java/com/thealgorithms/searches/InterpolationSearchTest.java +++ b/src/test/java/com/thealgorithms/searches/InterpolationSearchTest.java @@ -87,4 +87,15 @@ void testInterpolationSearchLargeNonUniformArray() { int key = 21; // Present in the array assertEquals(6, interpolationSearch.find(array, key), "The index of the found element should be 6."); } + + /** + * Test for interpolation search with specific sorted arrays that previously caused division by zero. + */ + @Test + void testInterpolationSearchDivisionByZeroEdgeCases() { + InterpolationSearch interpolationSearch = new InterpolationSearch(); + assertEquals(3, interpolationSearch.find(new int[] {0, 0, 0, 2}, 2)); + assertEquals(0, interpolationSearch.find(new int[] {2, 2, 2, 2}, 2)); + assertEquals(3, interpolationSearch.find(new int[] {0, 1, 2, 4}, 4)); + } } diff --git a/src/test/java/com/thealgorithms/slidingwindow/CountDistinctElementsInWindowTest.java b/src/test/java/com/thealgorithms/slidingwindow/CountDistinctElementsInWindowTest.java new file mode 100644 index 000000000000..a6931bca99d2 --- /dev/null +++ b/src/test/java/com/thealgorithms/slidingwindow/CountDistinctElementsInWindowTest.java @@ -0,0 +1,34 @@ +package com.thealgorithms.slidingwindow; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class CountDistinctElementsInWindowTest { + + @Test + public void testBasicCase() { + assertArrayEquals(new int[] {3, 2, 2}, CountDistinctElementsInWindow.countDistinct(new int[] {1, 2, 3, 2, 3}, 3)); + } + + @Test + public void testAllSame() { + assertArrayEquals(new int[] {1, 1, 1}, CountDistinctElementsInWindow.countDistinct(new int[] {2, 2, 2, 2}, 2)); + } + + @Test + public void testAllDistinct() { + assertArrayEquals(new int[] {3, 3}, CountDistinctElementsInWindow.countDistinct(new int[] {1, 2, 3, 4}, 3)); + } + + @Test + public void testWindowSizeEqualsArray() { + assertArrayEquals(new int[] {4}, CountDistinctElementsInWindow.countDistinct(new int[] {1, 2, 3, 4}, 4)); + } + + @Test + public void testInvalidInput() { + assertThrows(IllegalArgumentException.class, () -> CountDistinctElementsInWindow.countDistinct(new int[] {}, 2)); + } +} diff --git a/src/test/java/com/thealgorithms/sorts/LibrarySortTest.java b/src/test/java/com/thealgorithms/sorts/LibrarySortTest.java new file mode 100644 index 000000000000..49783f332e75 --- /dev/null +++ b/src/test/java/com/thealgorithms/sorts/LibrarySortTest.java @@ -0,0 +1,70 @@ +package com.thealgorithms.sorts; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class LibrarySortTest { + + @Test + public void testBasicSort() { + assertArrayEquals(new int[] {1, 2, 3, 4, 5}, LibrarySort.sort(new int[] {5, 3, 1, 4, 2})); + } + + @Test + public void testAlreadySorted() { + assertArrayEquals(new int[] {1, 2, 3, 4, 5}, LibrarySort.sort(new int[] {1, 2, 3, 4, 5})); + } + + @Test + public void testReverseSorted() { + assertArrayEquals(new int[] {1, 2, 3, 4, 5}, LibrarySort.sort(new int[] {5, 4, 3, 2, 1})); + } + + @Test + public void testDuplicates() { + assertArrayEquals(new int[] {1, 2, 2, 3, 3}, LibrarySort.sort(new int[] {3, 2, 1, 3, 2})); + } + + @Test + public void testSingleElement() { + assertArrayEquals(new int[] {1}, LibrarySort.sort(new int[] {1})); + } + + @Test + public void testEmptyArray() { + assertArrayEquals(new int[] {}, LibrarySort.sort(new int[] {})); + } + + @Test + public void testNullArray() { + assertThrows(IllegalArgumentException.class, () -> LibrarySort.sort(null)); + } + + // --- Added to cover branches the tests above never reach --- + + @Test + public void testShiftLeftWhenRightSideIsFull() { + // Right side of the target slot is completely occupied, forcing a left shift. + assertArrayEquals(new int[] {0, 1, 2, 3, 4, 5, 6}, LibrarySort.sort(new int[] {0, 1, 2, 6, 4, 5, 3})); + } + + @Test + public void testTieBreakPrefersRightWhenDistancesEqual() { + // A gap exists on both sides at equal distance; algorithm should favor the right shift. + assertArrayEquals(new int[] {0, 1, 2, 3}, LibrarySort.sort(new int[] {0, 1, 3, 2})); + } + + @Test + public void testRightSearchRunsOffTheEnd() { + // No gap anywhere to the right of the target slot, all the way to the array's end. + assertArrayEquals(new int[] {0, 1, 2, 3, 4, 5, 6, 7}, LibrarySort.sort(new int[] {0, 1, 2, 3, 4, 5, 7, 6})); + } + + @Test + public void testInsertAtEndWithNoTrailingGap() { + // A new global maximum arrives with no trailing gap left, forcing insertAtEnd(). + assertArrayEquals(new int[] {0, 1, 2, 3, 4, 5, 6, 7}, LibrarySort.sort(new int[] {0, 1, 2, 3, 4, 5, 6, 7})); + } +} diff --git a/src/test/java/com/thealgorithms/sorts/WiggleSortTest.java b/src/test/java/com/thealgorithms/sorts/WiggleSortTest.java index c5d57d63cf38..0d8b6acf9043 100644 --- a/src/test/java/com/thealgorithms/sorts/WiggleSortTest.java +++ b/src/test/java/com/thealgorithms/sorts/WiggleSortTest.java @@ -1,6 +1,7 @@ package com.thealgorithms.sorts; import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.Arrays; import org.junit.jupiter.api.Test; @@ -70,4 +71,22 @@ void wiggleTestStrings() { wiggleSort.sort(values); assertArrayEquals(values, result); } + + @Test + void wiggleTestNonWiggleSortableOddArrayThrows() { + // [1, 2, 2] is not wiggle-sortable: the median 2 appears ceil(3 / 2) = 2 times + // but is not the smallest value, so sorting must fail instead of returning + // a wrongly ordered array + WiggleSort wiggleSort = new WiggleSort(); + Integer[] values = {1, 2, 2}; + assertThrows(IllegalArgumentException.class, () -> wiggleSort.sort(values)); + } + + @Test + void wiggleTestTooManyDuplicatesThrows() { + // more than half of the values are the same, which can never be wiggle-sorted + WiggleSort wiggleSort = new WiggleSort(); + Integer[] values = {2, 2, 2, 1}; + assertThrows(IllegalArgumentException.class, () -> wiggleSort.sort(values)); + } } diff --git a/src/test/java/com/thealgorithms/strings/AlternativeStringArrangeTest.java b/src/test/java/com/thealgorithms/strings/AlternativeStringArrangeTest.java index 9e8ae9e9f153..4cd55a4d7410 100644 --- a/src/test/java/com/thealgorithms/strings/AlternativeStringArrangeTest.java +++ b/src/test/java/com/thealgorithms/strings/AlternativeStringArrangeTest.java @@ -1,9 +1,11 @@ package com.thealgorithms.strings; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; class AlternativeStringArrangeTest { @@ -20,4 +22,15 @@ private static Stream provideTestData() { void arrangeTest(String input1, String input2, String expected) { assertEquals(expected, AlternativeStringArrange.arrange(input1, input2)); } + + @ParameterizedTest(name = "null input ({0}, {1}) should throw IllegalArgumentException") + @MethodSource("provideNullInputs") + void arrangeThrowsOnNullInput(String input1, String input2) { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> AlternativeStringArrange.arrange(input1, input2)); + assertEquals("Input strings must not be null", ex.getMessage()); + } + + private static Stream provideNullInputs() { + return Stream.of(Arguments.of(null, "abc"), Arguments.of("abc", null), Arguments.of(null, null)); + } } diff --git a/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java b/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java new file mode 100644 index 000000000000..e54abcf2f1f3 --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java @@ -0,0 +1,36 @@ +package com.thealgorithms.strings; +// author: Vraj Prajapati @Rosander0 + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class LongestCommonSubstringTest { + + @Test + public void testNullOrEmptyInputs() { + assertEquals("", LongestCommonSubstring.longestCommonSubstring(null, "abc")); + assertEquals("", LongestCommonSubstring.longestCommonSubstring("abc", null)); + assertEquals("", LongestCommonSubstring.longestCommonSubstring("", "abc")); + assertEquals("", LongestCommonSubstring.longestCommonSubstring("abc", "")); + } + + @Test + public void testNormalSubstrings() { + assertEquals("cde", LongestCommonSubstring.longestCommonSubstring("abcdef", "zcdemf")); + assertEquals("abc", LongestCommonSubstring.longestCommonSubstring("abc", "abc")); + assertEquals("cdef", LongestCommonSubstring.longestCommonSubstring("abcdef", "cdefgh")); + } + + @Test + public void testSingleCharacterAndNoMatch() { + assertEquals("a", LongestCommonSubstring.longestCommonSubstring("a", "a")); + assertEquals("", LongestCommonSubstring.longestCommonSubstring("abc", "xyz")); + } + + @Test + public void testMultipleMatchesFirstLongest() { + // Keeps the first matched longest substring when lengths are tied + assertEquals("abc", LongestCommonSubstring.longestCommonSubstring("abcXdef", "abcYdef")); + } +} diff --git a/src/test/java/com/thealgorithms/strings/TitleCaseTest.java b/src/test/java/com/thealgorithms/strings/TitleCaseTest.java new file mode 100644 index 000000000000..37bb44ed2c7b --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/TitleCaseTest.java @@ -0,0 +1,39 @@ +package com.thealgorithms.strings; +// author: Vraj Prajapati @Rosander0 + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class TitleCaseTest { + + @Test + public void testNullOrEmptyInputs() { + assertEquals("", TitleCase.toTitleCase(null)); + assertEquals("", TitleCase.toTitleCase("")); + } + + @Test + public void testSingleWord() { + assertEquals("Hello", TitleCase.toTitleCase("hello")); + assertEquals("Hello", TitleCase.toTitleCase("HELLO")); + assertEquals("A", TitleCase.toTitleCase("a")); + } + + @Test + public void testMultipleWords() { + assertEquals("The Quick Brown Fox", TitleCase.toTitleCase("the quick brown fox")); + assertEquals("The Quick Brown Fox", TitleCase.toTitleCase("THE QUICK BROWN FOX")); + assertEquals("Already Title Case", TitleCase.toTitleCase("already Title Case")); + } + + @Test + public void testWhitespace() { + assertEquals(" Spaces ", TitleCase.toTitleCase(" spaces ")); + } + + @Test + public void testQuotedWords() { + assertEquals("\"Hello\"", TitleCase.toTitleCase("\"hello\"")); + } +}