From e19378d56c5d3f741a6b8402e4c8815fb38a6f88 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:18:55 +0530 Subject: [PATCH 01/75] Add tests, remove `main` in `RangeInSortedArray` (#5778) --- DIRECTORY.md | 1 + .../misc/RangeInSortedArray.java | 9 ----- .../misc/RangeInSortedArrayTest.java | 35 +++++++++++++++++++ 3 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index f354913a05e..c23cb4a3490 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1018,6 +1018,7 @@ * [MedianOfRunningArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/MedianOfRunningArrayTest.java) * [MirrorOfMatrixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/MirrorOfMatrixTest.java) * [PalindromeSinglyLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java) + * [RangeInSortedArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java) * [TwoSumProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/TwoSumProblemTest.java) * others * [ArrayLeftRotationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/ArrayLeftRotationTest.java) diff --git a/src/main/java/com/thealgorithms/misc/RangeInSortedArray.java b/src/main/java/com/thealgorithms/misc/RangeInSortedArray.java index 0dfc8ac32a6..6d3caa1814b 100644 --- a/src/main/java/com/thealgorithms/misc/RangeInSortedArray.java +++ b/src/main/java/com/thealgorithms/misc/RangeInSortedArray.java @@ -1,18 +1,9 @@ package com.thealgorithms.misc; -import java.util.Arrays; - public final class RangeInSortedArray { private RangeInSortedArray() { } - public static void main(String[] args) { - // Testcases - assert Arrays.equals(sortedRange(new int[] {1, 2, 3, 3, 3, 4, 5}, 3), new int[] {2, 4}); - assert Arrays.equals(sortedRange(new int[] {1, 2, 3, 3, 3, 4, 5}, 4), new int[] {5, 5}); - assert Arrays.equals(sortedRange(new int[] {0, 1, 2}, 3), new int[] {-1, -1}); - } - // Get the 1st and last occurrence index of a number 'key' in a non-decreasing array 'nums' // Gives [-1, -1] in case element doesn't exist in array public static int[] sortedRange(int[] nums, int key) { diff --git a/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java b/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java new file mode 100644 index 00000000000..7630d3e78dc --- /dev/null +++ b/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java @@ -0,0 +1,35 @@ +package com.thealgorithms.misc; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class RangeInSortedArrayTest { + + @ParameterizedTest(name = "Test case {index}: {3}") + @MethodSource("provideSortedRangeTestCases") + void testSortedRange(int[] nums, int key, int[] expectedRange, String description) { + assertArrayEquals(expectedRange, RangeInSortedArray.sortedRange(nums, key), description); + } + + private static Stream provideSortedRangeTestCases() { + return Stream.of(Arguments.of(new int[] {1, 2, 3, 3, 3, 4, 5}, 3, new int[] {2, 4}, "Range for key 3 with multiple occurrences"), Arguments.of(new int[] {1, 2, 3, 3, 3, 4, 5}, 4, new int[] {5, 5}, "Range for key 4 with single occurrence"), + Arguments.of(new int[] {0, 1, 2}, 3, new int[] {-1, -1}, "Range for non-existent key"), Arguments.of(new int[] {}, 1, new int[] {-1, -1}, "Range in empty array"), Arguments.of(new int[] {1, 1, 1, 2, 3, 4, 5, 5, 5}, 1, new int[] {0, 2}, "Range for key at start"), + Arguments.of(new int[] {1, 1, 1, 2, 3, 4, 5, 5, 5}, 5, new int[] {6, 8}, "Range for key at end")); + } + + @ParameterizedTest(name = "Test case {index}: {3}") + @MethodSource("provideGetCountLessThanTestCases") + void testGetCountLessThan(int[] nums, int key, int expectedCount, String description) { + assertEquals(expectedCount, RangeInSortedArray.getCountLessThan(nums, key), description); + } + + private static Stream provideGetCountLessThanTestCases() { + return Stream.of(Arguments.of(new int[] {1, 2, 3, 3, 4, 5}, 3, 4, "Count of elements less than existing key"), Arguments.of(new int[] {1, 2, 3, 3, 4, 5}, 4, 5, "Count of elements less than non-existing key"), Arguments.of(new int[] {1, 2, 2, 3}, 5, 4, "Count with all smaller elements"), + Arguments.of(new int[] {2, 3, 4, 5}, 1, 0, "Count with no smaller elements"), Arguments.of(new int[] {}, 1, 0, "Count in empty array")); + } +} From 8d8834987a4fe925fe2f5b2039dc37f9e63c1859 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:40:18 +0530 Subject: [PATCH 02/75] Add tests, remove `main` in `EulerMethod` (#5771) --- DIRECTORY.md | 1 + .../thealgorithms/misc/MatrixTranspose.java | 73 +++++-------------- .../misc/MatrixTransposeTest.java | 33 +++++++++ 3 files changed, 52 insertions(+), 55 deletions(-) create mode 100644 src/test/java/com/thealgorithms/misc/MatrixTransposeTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index c23cb4a3490..b756b8d1c75 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1014,6 +1014,7 @@ * [ColorContrastRatioTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/ColorContrastRatioTest.java) * [InverseOfMatrixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/InverseOfMatrixTest.java) * [MapReduceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/MapReduceTest.java) + * [MatrixTransposeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/MatrixTransposeTest.java) * [MedianOfMatrixtest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/MedianOfMatrixtest.java) * [MedianOfRunningArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/MedianOfRunningArrayTest.java) * [MirrorOfMatrixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/MirrorOfMatrixTest.java) diff --git a/src/main/java/com/thealgorithms/misc/MatrixTranspose.java b/src/main/java/com/thealgorithms/misc/MatrixTranspose.java index 153cf4e9df9..743682780b0 100644 --- a/src/main/java/com/thealgorithms/misc/MatrixTranspose.java +++ b/src/main/java/com/thealgorithms/misc/MatrixTranspose.java @@ -1,7 +1,5 @@ package com.thealgorithms.misc; -import java.util.Scanner; - /** * * @@ -22,62 +20,27 @@ public final class MatrixTranspose { private MatrixTranspose() { } - public static void main(String[] args) { - /* - * This is the main method - * - * @param args Unused. - * - * @return Nothing. - */ - Scanner sc = new Scanner(System.in); - int i; - int j; - int row; - int column; - System.out.println("Enter the number of rows in the 2D matrix:"); - - /* - * Take input from user for how many rows to be print - */ - row = sc.nextInt(); - - System.out.println("Enter the number of columns in the 2D matrix:"); - - /* - * Take input from user for how many coloumn to be print - */ - column = sc.nextInt(); - int[][] arr = new int[row][column]; - System.out.println("Enter the elements"); - for (i = 0; i < row; i++) { - for (j = 0; j < column; j++) { - arr[i][j] = sc.nextInt(); - } - } - - /* - * Print matrix before the Transpose in proper way - */ - System.out.println("The matrix is:"); - for (i = 0; i < row; i++) { - for (j = 0; j < column; j++) { - System.out.print(arr[i][j] + "\t"); - } - System.out.print("\n"); + /** + * Calculate the transpose of the given matrix. + * + * @param matrix The matrix to be transposed + * @throws IllegalArgumentException if the matrix is empty + * @throws NullPointerException if the matrix is null + * @return The transposed matrix + */ + public static int[][] transpose(int[][] matrix) { + if (matrix == null || matrix.length == 0) { + throw new IllegalArgumentException("Matrix is empty"); } - /* - * Print matrix after the tranpose in proper way Transpose means Interchanging - * of rows wth column so we interchange the rows in next loop Thus at last - * matrix of transpose is obtained through user input... - */ - System.out.println("The Transpose of the given matrix is:"); - for (i = 0; i < column; i++) { - for (j = 0; j < row; j++) { - System.out.print(arr[j][i] + "\t"); + int rows = matrix.length; + int cols = matrix[0].length; + int[][] transposedMatrix = new int[cols][rows]; + for (int i = 0; i < cols; i++) { + for (int j = 0; j < rows; j++) { + transposedMatrix[i][j] = matrix[j][i]; } - System.out.print("\n"); } + return transposedMatrix; } } diff --git a/src/test/java/com/thealgorithms/misc/MatrixTransposeTest.java b/src/test/java/com/thealgorithms/misc/MatrixTransposeTest.java new file mode 100644 index 00000000000..cf668807b81 --- /dev/null +++ b/src/test/java/com/thealgorithms/misc/MatrixTransposeTest.java @@ -0,0 +1,33 @@ +package com.thealgorithms.misc; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class MatrixTransposeTest { + + private static Stream provideValidMatrixTestCases() { + return Stream.of(Arguments.of(new int[][] {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}, new int[][] {{1, 4, 7}, {2, 5, 8}, {3, 6, 9}}, "Transpose of square matrix"), Arguments.of(new int[][] {{1, 2}, {3, 4}, {5, 6}}, new int[][] {{1, 3, 5}, {2, 4, 6}}, "Transpose of rectangular matrix"), + Arguments.of(new int[][] {{1, 2, 3}}, new int[][] {{1}, {2}, {3}}, "Transpose of single-row matrix"), Arguments.of(new int[][] {{1}, {2}, {3}}, new int[][] {{1, 2, 3}}, "Transpose of single-column matrix")); + } + + private static Stream provideInvalidMatrixTestCases() { + return Stream.of(Arguments.of(new int[0][0], "Empty matrix should throw IllegalArgumentException"), Arguments.of(null, "Null matrix should throw IllegalArgumentException")); + } + + @ParameterizedTest(name = "Test case {index}: {2}") + @MethodSource("provideValidMatrixTestCases") + void testValidMatrixTranspose(int[][] input, int[][] expected, String description) { + assertArrayEquals(expected, MatrixTranspose.transpose(input), description); + } + + @ParameterizedTest(name = "Test case {index}: {1}") + @MethodSource("provideInvalidMatrixTestCases") + void testInvalidMatrixTranspose(int[][] input, String description) { + assertThrows(IllegalArgumentException.class, () -> MatrixTranspose.transpose(input), description); + } +} From bcf4034ce58ed965d1d885e1b0bf85a920f57e2d Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:57:06 +0530 Subject: [PATCH 03/75] Add tests, remove `main` in `WordBoggle` (#5782) --- DIRECTORY.md | 1 + .../com/thealgorithms/misc/WordBoggle.java | 31 ---------- .../thealgorithms/misc/WordBoggleTest.java | 56 +++++++++++++++++++ 3 files changed, 57 insertions(+), 31 deletions(-) create mode 100644 src/test/java/com/thealgorithms/misc/WordBoggleTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index b756b8d1c75..4c454088088 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1021,6 +1021,7 @@ * [PalindromeSinglyLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java) * [RangeInSortedArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java) * [TwoSumProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/TwoSumProblemTest.java) + * [WordBoggleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/WordBoggleTest.java) * others * [ArrayLeftRotationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/ArrayLeftRotationTest.java) * [ArrayRightRotationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/ArrayRightRotationTest.java) diff --git a/src/main/java/com/thealgorithms/misc/WordBoggle.java b/src/main/java/com/thealgorithms/misc/WordBoggle.java index 3eb0dc95ffb..8b629d68209 100644 --- a/src/main/java/com/thealgorithms/misc/WordBoggle.java +++ b/src/main/java/com/thealgorithms/misc/WordBoggle.java @@ -1,7 +1,6 @@ package com.thealgorithms.misc; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -32,36 +31,6 @@ public static List boggleBoard(char[][] board, String[] words) { return new ArrayList<>(finalWords); } - public static void main(String[] args) { - // Testcase - List ans = new ArrayList<>(Arrays.asList("a", "boggle", "this", "NOTRE_PEATED", "is", "simple", "board")); - assert (boggleBoard( - new char[][] { - {'t', 'h', 'i', 's', 'i', 's', 'a'}, - {'s', 'i', 'm', 'p', 'l', 'e', 'x'}, - {'b', 'x', 'x', 'x', 'x', 'e', 'b'}, - {'x', 'o', 'g', 'g', 'l', 'x', 'o'}, - {'x', 'x', 'x', 'D', 'T', 'r', 'a'}, - {'R', 'E', 'P', 'E', 'A', 'd', 'x'}, - {'x', 'x', 'x', 'x', 'x', 'x', 'x'}, - {'N', 'O', 'T', 'R', 'E', '_', 'P'}, - {'x', 'x', 'D', 'E', 'T', 'A', 'E'}, - }, - new String[] { - "this", - "is", - "not", - "a", - "simple", - "test", - "boggle", - "board", - "REPEATED", - "NOTRE_PEATED", - }) - .equals(ans)); - } - public static void explore(int i, int j, char[][] board, TrieNode trieNode, boolean[][] visited, Set finalWords) { if (visited[i][j]) { return; diff --git a/src/test/java/com/thealgorithms/misc/WordBoggleTest.java b/src/test/java/com/thealgorithms/misc/WordBoggleTest.java new file mode 100644 index 00000000000..2c79ec79656 --- /dev/null +++ b/src/test/java/com/thealgorithms/misc/WordBoggleTest.java @@ -0,0 +1,56 @@ +package com.thealgorithms.misc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class WordBoggleTest { + private char[][] board; + + @BeforeEach + void setup() { + board = new char[][] { + {'t', 'h', 'i', 's', 'i', 's', 'a'}, + {'s', 'i', 'm', 'p', 'l', 'e', 'x'}, + {'b', 'x', 'x', 'x', 'x', 'e', 'b'}, + {'x', 'o', 'g', 'g', 'l', 'x', 'o'}, + {'x', 'x', 'x', 'D', 'T', 'r', 'a'}, + {'R', 'E', 'P', 'E', 'A', 'd', 'x'}, + {'x', 'x', 'x', 'x', 'x', 'x', 'x'}, + {'N', 'O', 'T', 'R', 'E', '_', 'P'}, + {'x', 'x', 'D', 'E', 'T', 'A', 'E'}, + }; + } + + @ParameterizedTest + @MethodSource("provideTestCases") + void testBoggleBoard(String[] words, List expectedWords, String testDescription) { + List result = WordBoggle.boggleBoard(board, words); + assertEquals(expectedWords.size(), result.size(), "Test failed for: " + testDescription); + assertTrue(expectedWords.containsAll(result), "Test failed for: " + testDescription); + } + + private static Stream provideTestCases() { + return Stream.of(Arguments.of(new String[] {"this", "is", "not", "a", "simple", "test", "boggle", "board", "REPEATED", "NOTRE_PEATED"}, Arrays.asList("this", "is", "a", "simple", "board", "boggle", "NOTRE_PEATED"), "All words"), + Arguments.of(new String[] {"xyz", "hello", "world"}, List.of(), "No matching words"), Arguments.of(new String[] {}, List.of(), "Empty words array"), Arguments.of(new String[] {"this", "this", "board", "board"}, Arrays.asList("this", "board"), "Duplicate words in input")); + } + + @ParameterizedTest + @MethodSource("provideSpecialCases") + void testBoggleBoardSpecialCases(char[][] specialBoard, String[] words, List expectedWords, String testDescription) { + List result = WordBoggle.boggleBoard(specialBoard, words); + assertEquals(expectedWords.size(), result.size(), "Test failed for: " + testDescription); + assertTrue(expectedWords.containsAll(result), "Test failed for: " + testDescription); + } + + private static Stream provideSpecialCases() { + return Stream.of(Arguments.of(new char[0][0], new String[] {"this", "is", "a", "test"}, List.of(), "Empty board")); + } +} From 1dfa2e571bd3ad61056aca611911733918d5e359 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Mon, 14 Oct 2024 23:21:59 +0530 Subject: [PATCH 04/75] feat: Add `ConvexHull` new algorithm with Junit tests (#5789) --- DIRECTORY.md | 3 + .../thealgorithms/geometry/ConvexHull.java | 116 ++++++++++++++++++ .../thealgorithms/geometry/GrahamScan.java | 90 -------------- .../com/thealgorithms/geometry/Point.java | 45 +++++++ .../geometry/ConvexHullTest.java | 40 ++++++ .../geometry/GrahamScanTest.java | 2 +- 6 files changed, 205 insertions(+), 91 deletions(-) create mode 100644 src/main/java/com/thealgorithms/geometry/ConvexHull.java create mode 100644 src/main/java/com/thealgorithms/geometry/Point.java create mode 100644 src/test/java/com/thealgorithms/geometry/ConvexHullTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 4c454088088..a1feab7bef2 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -304,7 +304,9 @@ * [WildcardMatching](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/WildcardMatching.java) * [WineProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java) * geometry + * [ConvexHull](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/ConvexHull.java) * [GrahamScan](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/GrahamScan.java) + * [Point](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/Point.java) * greedyalgorithms * [ActivitySelection](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/ActivitySelection.java) * [BinaryAddition](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/BinaryAddition.java) @@ -896,6 +898,7 @@ * [WildcardMatchingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/WildcardMatchingTest.java) * [WineProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java) * geometry + * [ConvexHullTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/ConvexHullTest.java) * [GrahamScanTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/GrahamScanTest.java) * greedyalgorithms * [ActivitySelectionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/ActivitySelectionTest.java) diff --git a/src/main/java/com/thealgorithms/geometry/ConvexHull.java b/src/main/java/com/thealgorithms/geometry/ConvexHull.java new file mode 100644 index 00000000000..19cecdc3a3a --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/ConvexHull.java @@ -0,0 +1,116 @@ +package com.thealgorithms.geometry; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +/** + * A class providing algorithms to compute the convex hull of a set of points + * using brute-force and recursive methods. + * + * Convex hull: The smallest convex polygon that contains all the given points. + * + * Algorithms provided: + * 1. Brute-Force Method + * 2. Recursive (Divide-and-Conquer) Method + * + * @author Hardvan + */ +public final class ConvexHull { + private ConvexHull() { + } + + private static boolean checkPointOrientation(Point i, Point j, Point k) { + int detK = Point.orientation(i, j, k); + if (detK > 0) { + return true; // pointsLeftOfIJ + } else if (detK < 0) { + return false; // pointsRightOfIJ + } else { + return k.compareTo(i) >= 0 && k.compareTo(j) <= 0; + } + } + + public static List convexHullBruteForce(List points) { + Set convexSet = new TreeSet<>(Comparator.naturalOrder()); + + for (int i = 0; i < points.size() - 1; i++) { + for (int j = i + 1; j < points.size(); j++) { + boolean allPointsOnOneSide = true; + boolean leftSide = checkPointOrientation(points.get(i), points.get(j), points.get((i + 1) % points.size())); + + for (int k = 0; k < points.size(); k++) { + if (k != i && k != j && checkPointOrientation(points.get(i), points.get(j), points.get(k)) != leftSide) { + allPointsOnOneSide = false; + break; + } + } + + if (allPointsOnOneSide) { + convexSet.add(points.get(i)); + convexSet.add(points.get(j)); + } + } + } + + return new ArrayList<>(convexSet); + } + + public static List convexHullRecursive(List points) { + Collections.sort(points); + Set convexSet = new HashSet<>(); + Point leftMostPoint = points.get(0); + Point rightMostPoint = points.get(points.size() - 1); + + convexSet.add(leftMostPoint); + convexSet.add(rightMostPoint); + + List upperHull = new ArrayList<>(); + List lowerHull = new ArrayList<>(); + + for (int i = 1; i < points.size() - 1; i++) { + int det = Point.orientation(leftMostPoint, rightMostPoint, points.get(i)); + if (det > 0) { + upperHull.add(points.get(i)); + } else if (det < 0) { + lowerHull.add(points.get(i)); + } + } + + constructHull(upperHull, leftMostPoint, rightMostPoint, convexSet); + constructHull(lowerHull, rightMostPoint, leftMostPoint, convexSet); + + List result = new ArrayList<>(convexSet); + Collections.sort(result); + return result; + } + + private static void constructHull(List points, Point left, Point right, Set convexSet) { + if (!points.isEmpty()) { + Point extremePoint = null; + int extremePointDistance = Integer.MIN_VALUE; + List candidatePoints = new ArrayList<>(); + + for (Point p : points) { + int det = Point.orientation(left, right, p); + if (det > 0) { + candidatePoints.add(p); + if (det > extremePointDistance) { + extremePointDistance = det; + extremePoint = p; + } + } + } + + if (extremePoint != null) { + constructHull(candidatePoints, left, extremePoint, convexSet); + convexSet.add(extremePoint); + constructHull(candidatePoints, extremePoint, right, convexSet); + } + } + } +} diff --git a/src/main/java/com/thealgorithms/geometry/GrahamScan.java b/src/main/java/com/thealgorithms/geometry/GrahamScan.java index 1a36137895e..1a373cf315a 100644 --- a/src/main/java/com/thealgorithms/geometry/GrahamScan.java +++ b/src/main/java/com/thealgorithms/geometry/GrahamScan.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; import java.util.Stack; /** @@ -66,93 +65,4 @@ public GrahamScan(Point[] points) { public Iterable hull() { return new ArrayList<>(hull); } - - public record Point(int x, int y) implements Comparable { - - /** - * Default constructor - * @param x x-coordinate - * @param y y-coordinate - */ - public Point { - } - - /** - * @return the x-coordinate - */ - @Override - public int x() { - return x; - } - - /** - * @return the y-coordinate - */ - @Override - public int y() { - return y; - } - - /** - * Determines the orientation of the triplet (a, b, c). - * - * @param a The first point - * @param b The second point - * @param c The third point - * @return -1 if (a, b, c) is clockwise, 0 if collinear, +1 if counterclockwise - */ - public static int orientation(Point a, Point b, Point c) { - int val = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); - return Integer.compare(val, 0); - } - - /** - * Compares this point with another point. - * - * @param p2 The point to compare to - * @return A positive integer if this point is greater, a negative integer if less, or 0 if equal - */ - @Override - public int compareTo(Point p2) { - int cmpY = Integer.compare(this.y, p2.y); - return cmpY != 0 ? cmpY : Integer.compare(this.x, p2.x); - } - - /** - * Returns a comparator to sort points by their polar order relative to this point. - * - * @return A polar order comparator - */ - public Comparator polarOrder() { - return new PolarOrder(); - } - - private final class PolarOrder implements Comparator { - @Override - public int compare(Point p1, Point p2) { - int dx1 = p1.x - x; - int dy1 = p1.y - y; - int dx2 = p2.x - x; - int dy2 = p2.y - y; - - if (dy1 >= 0 && dy2 < 0) { - return -1; // p1 above p2 - } else if (dy2 >= 0 && dy1 < 0) { - return 1; // p1 below p2 - } else if (dy1 == 0 && dy2 == 0) { // Collinear and horizontal - return Integer.compare(dx2, dx1); - } else { - return -orientation(Point.this, p1, p2); // Compare orientation - } - } - } - - /** - * @return A string representation of this point in the format (x, y) - */ - @Override - public String toString() { - return String.format("(%d, %d)", x, y); - } - } } diff --git a/src/main/java/com/thealgorithms/geometry/Point.java b/src/main/java/com/thealgorithms/geometry/Point.java new file mode 100644 index 00000000000..564edb4ba7b --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/Point.java @@ -0,0 +1,45 @@ +package com.thealgorithms.geometry; + +import java.util.Comparator; + +public record Point(int x, int y) implements Comparable { + + @Override + public int compareTo(Point other) { + int cmpY = Integer.compare(this.y, other.y); + return cmpY != 0 ? cmpY : Integer.compare(this.x, other.x); + } + + @Override + public String toString() { + return String.format("(%d, %d)", x, y); + } + + public Comparator polarOrder() { + return new PolarOrder(); + } + + public static int orientation(Point a, Point b, Point c) { + return Integer.compare((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x), 0); + } + + private final class PolarOrder implements Comparator { + @Override + public int compare(Point p1, Point p2) { + int dx1 = p1.x - x; + int dy1 = p1.y - y; + int dx2 = p2.x - x; + int dy2 = p2.y - y; + + if (dy1 >= 0 && dy2 < 0) { + return -1; // p1 above p2 + } else if (dy2 >= 0 && dy1 < 0) { + return 1; // p1 below p2 + } else if (dy1 == 0 && dy2 == 0) { // Collinear and horizontal + return Integer.compare(dx2, dx1); + } else { + return -orientation(Point.this, p1, p2); // Compare orientation + } + } + } +} diff --git a/src/test/java/com/thealgorithms/geometry/ConvexHullTest.java b/src/test/java/com/thealgorithms/geometry/ConvexHullTest.java new file mode 100644 index 00000000000..e3e32e43c6d --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/ConvexHullTest.java @@ -0,0 +1,40 @@ +package com.thealgorithms.geometry; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class ConvexHullTest { + + @Test + void testConvexHullBruteForce() { + List points = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 1)); + List expected = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 1)); + assertEquals(expected, ConvexHull.convexHullBruteForce(points)); + + points = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 0)); + expected = Arrays.asList(new Point(0, 0), new Point(10, 0)); + assertEquals(expected, ConvexHull.convexHullBruteForce(points)); + + points = Arrays.asList(new Point(0, 3), new Point(2, 2), new Point(1, 1), new Point(2, 1), new Point(3, 0), new Point(0, 0), new Point(3, 3), new Point(2, -1), new Point(2, -4), new Point(1, -3)); + expected = Arrays.asList(new Point(2, -4), new Point(1, -3), new Point(0, 0), new Point(3, 0), new Point(0, 3), new Point(3, 3)); + assertEquals(expected, ConvexHull.convexHullBruteForce(points)); + } + + @Test + void testConvexHullRecursive() { + List points = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 1)); + List expected = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 1)); + assertEquals(expected, ConvexHull.convexHullRecursive(points)); + + points = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 0)); + expected = Arrays.asList(new Point(0, 0), new Point(10, 0)); + assertEquals(expected, ConvexHull.convexHullRecursive(points)); + + points = Arrays.asList(new Point(0, 3), new Point(2, 2), new Point(1, 1), new Point(2, 1), new Point(3, 0), new Point(0, 0), new Point(3, 3), new Point(2, -1), new Point(2, -4), new Point(1, -3)); + expected = Arrays.asList(new Point(2, -4), new Point(1, -3), new Point(0, 0), new Point(3, 0), new Point(0, 3), new Point(3, 3)); + assertEquals(expected, ConvexHull.convexHullRecursive(points)); + } +} diff --git a/src/test/java/com/thealgorithms/geometry/GrahamScanTest.java b/src/test/java/com/thealgorithms/geometry/GrahamScanTest.java index e59cd6b860c..622273881a2 100644 --- a/src/test/java/com/thealgorithms/geometry/GrahamScanTest.java +++ b/src/test/java/com/thealgorithms/geometry/GrahamScanTest.java @@ -7,7 +7,7 @@ public class GrahamScanTest { @Test void testGrahamScan() { - GrahamScan.Point[] points = {new GrahamScan.Point(0, 3), new GrahamScan.Point(1, 1), new GrahamScan.Point(2, 2), new GrahamScan.Point(4, 4), new GrahamScan.Point(0, 0), new GrahamScan.Point(1, 2), new GrahamScan.Point(3, 1), new GrahamScan.Point(3, 3)}; + Point[] points = {new Point(0, 3), new Point(1, 1), new Point(2, 2), new Point(4, 4), new Point(0, 0), new Point(1, 2), new Point(3, 1), new Point(3, 3)}; String expectedResult = "[(0, 0), (3, 1), (4, 4), (0, 3)]"; GrahamScan graham = new GrahamScan(points); From 6ef1f7ca01823b8f533173a318727bd5b1f1e2e5 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Mon, 14 Oct 2024 23:26:05 +0530 Subject: [PATCH 05/75] Add tests, remove `main` in `ThreeSumProblem` (#5781) --- DIRECTORY.md | 1 + .../thealgorithms/misc/ThreeSumProblem.java | 19 ------- .../misc/ThreeSumProblemTest.java | 52 +++++++++++++++++++ 3 files changed, 53 insertions(+), 19 deletions(-) create mode 100644 src/test/java/com/thealgorithms/misc/ThreeSumProblemTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index a1feab7bef2..265d1aeeb89 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1023,6 +1023,7 @@ * [MirrorOfMatrixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/MirrorOfMatrixTest.java) * [PalindromeSinglyLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java) * [RangeInSortedArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java) + * [ThreeSumProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/ThreeSumProblemTest.java) * [TwoSumProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/TwoSumProblemTest.java) * [WordBoggleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/WordBoggleTest.java) * others diff --git a/src/main/java/com/thealgorithms/misc/ThreeSumProblem.java b/src/main/java/com/thealgorithms/misc/ThreeSumProblem.java index 1c5f4a44053..8ef10758ef8 100644 --- a/src/main/java/com/thealgorithms/misc/ThreeSumProblem.java +++ b/src/main/java/com/thealgorithms/misc/ThreeSumProblem.java @@ -7,29 +7,10 @@ import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; -import java.util.Scanner; import java.util.Set; public class ThreeSumProblem { - public static void main(String[] args) { - Scanner scan = new Scanner(System.in); - System.out.print("Enter the target sum "); - int ts = scan.nextInt(); - System.out.print("Enter the number of elements in the array "); - int n = scan.nextInt(); - System.out.println("Enter all your array elements:"); - int[] arr = new int[n]; - for (int i = 0; i < n; i++) { - arr[i] = scan.nextInt(); - } - ThreeSumProblem th = new ThreeSumProblem(); - System.out.println("Brute Force Approach\n" + (th.bruteForce(arr, ts)) + "\n"); - System.out.println("Two Pointer Approach\n" + (th.twoPointer(arr, ts)) + "\n"); - System.out.println("Hashmap Approach\n" + (th.hashMap(arr, ts))); - scan.close(); - } - public List> bruteForce(int[] nums, int target) { List> arr = new ArrayList>(); diff --git a/src/test/java/com/thealgorithms/misc/ThreeSumProblemTest.java b/src/test/java/com/thealgorithms/misc/ThreeSumProblemTest.java new file mode 100644 index 00000000000..5353168216e --- /dev/null +++ b/src/test/java/com/thealgorithms/misc/ThreeSumProblemTest.java @@ -0,0 +1,52 @@ +package com.thealgorithms.misc; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class ThreeSumProblemTest { + + private ThreeSumProblem tsp; + + @BeforeEach + public void setup() { + tsp = new ThreeSumProblem(); + } + + @ParameterizedTest + @MethodSource("bruteForceTestProvider") + public void testBruteForce(int[] nums, int target, List> expected) { + assertEquals(expected, tsp.bruteForce(nums, target)); + } + + @ParameterizedTest + @MethodSource("twoPointerTestProvider") + public void testTwoPointer(int[] nums, int target, List> expected) { + assertEquals(expected, tsp.twoPointer(nums, target)); + } + + @ParameterizedTest + @MethodSource("hashMapTestProvider") + public void testHashMap(int[] nums, int target, List> expected) { + assertEquals(expected, tsp.hashMap(nums, target)); + } + + private static Stream bruteForceTestProvider() { + return Stream.of(Arguments.of(new int[] {1, 2, -3, 4, -2, -1}, 0, Arrays.asList(Arrays.asList(-3, 1, 2), Arrays.asList(-3, -1, 4))), Arguments.of(new int[] {1, 2, 3, 4, 5}, 50, new ArrayList<>())); + } + + private static Stream twoPointerTestProvider() { + return Stream.of(Arguments.of(new int[] {0, -1, 2, -3, 1}, 0, Arrays.asList(Arrays.asList(-3, 1, 2), Arrays.asList(-1, 0, 1))), Arguments.of(new int[] {-5, -4, -3, -2, -1}, -10, Arrays.asList(Arrays.asList(-5, -4, -1), Arrays.asList(-5, -3, -2)))); + } + + private static Stream hashMapTestProvider() { + return Stream.of(Arguments.of(new int[] {1, 2, -1, -4, 3, 0}, 2, Arrays.asList(Arrays.asList(-1, 0, 3), Arrays.asList(-1, 1, 2))), Arguments.of(new int[] {5, 7, 9, 11}, 10, new ArrayList<>()), Arguments.of(new int[] {}, 0, new ArrayList<>())); + } +} From 3af4cfd7c8e83df8f7018d802c61f9c22296e729 Mon Sep 17 00:00:00 2001 From: Byte Bender <136265142+BYT-Bender@users.noreply.github.com> Date: Tue, 15 Oct 2024 01:32:40 +0530 Subject: [PATCH 06/75] Add SumOfOddNumbers (#5730) --- .../thealgorithms/maths/SumOfOddNumbers.java | 25 +++++++++++++++++ .../maths/SumOfOddNumbersTest.java | 28 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/main/java/com/thealgorithms/maths/SumOfOddNumbers.java create mode 100644 src/test/java/com/thealgorithms/maths/SumOfOddNumbersTest.java diff --git a/src/main/java/com/thealgorithms/maths/SumOfOddNumbers.java b/src/main/java/com/thealgorithms/maths/SumOfOddNumbers.java new file mode 100644 index 00000000000..c0a1e782659 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/SumOfOddNumbers.java @@ -0,0 +1,25 @@ +package com.thealgorithms.maths; + +/** + * This program calculates the sum of the first n odd numbers. + * + * https://www.cuemath.com/algebra/sum-of-odd-numbers/ + */ + +public final class SumOfOddNumbers { + private SumOfOddNumbers() { + } + + /** + * Calculate sum of the first n odd numbers + * + * @param n the number of odd numbers to sum + * @return sum of the first n odd numbers + */ + public static int sumOfFirstNOddNumbers(final int n) { + if (n < 0) { + throw new IllegalArgumentException("n must be non-negative."); + } + return n * n; + } +} diff --git a/src/test/java/com/thealgorithms/maths/SumOfOddNumbersTest.java b/src/test/java/com/thealgorithms/maths/SumOfOddNumbersTest.java new file mode 100644 index 00000000000..6470d798388 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/SumOfOddNumbersTest.java @@ -0,0 +1,28 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.stream.Stream; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class SumOfOddNumbersTest { + + @ParameterizedTest + @MethodSource("inputStream") + void sumOfFirstNOddNumbersTests(int expected, int input) { + Assertions.assertEquals(expected, SumOfOddNumbers.sumOfFirstNOddNumbers(input)); + } + + private static Stream inputStream() { + return Stream.of(Arguments.of(1, 1), Arguments.of(4, 2), Arguments.of(9, 3), Arguments.of(16, 4), Arguments.of(25, 5), Arguments.of(100, 10)); + } + + @Test + public void testSumOfFirstNOddNumbersThrowsExceptionForNegativeInput() { + assertThrows(IllegalArgumentException.class, () -> SumOfOddNumbers.sumOfFirstNOddNumbers(-1)); + } +} From 8886e0996a793d3a5573609a331c31709160ce4d Mon Sep 17 00:00:00 2001 From: Piotr Idzik <65706193+vil02@users.noreply.github.com> Date: Mon, 14 Oct 2024 22:46:28 +0200 Subject: [PATCH 07/75] style: include `OCP_OVERLY_CONCRETE_PARAMETER` (#5833) --- spotbugs-exclude.xml | 3 --- .../java/com/thealgorithms/backtracking/CrosswordSolver.java | 3 ++- .../java/com/thealgorithms/datastructures/graphs/AStar.java | 2 +- .../datastructures/graphs/EdmondsBlossomAlgorithm.java | 2 +- .../datastructures/lists/MergeSortedArrayList.java | 3 ++- src/main/java/com/thealgorithms/geometry/ConvexHull.java | 3 ++- .../java/com/thealgorithms/maths/CircularConvolutionFFT.java | 3 ++- src/main/java/com/thealgorithms/maths/ConvolutionFFT.java | 3 ++- src/main/java/com/thealgorithms/maths/FFT.java | 3 ++- src/main/java/com/thealgorithms/maths/FFTBluestein.java | 3 ++- src/main/java/com/thealgorithms/maths/Gaussian.java | 3 ++- src/main/java/com/thealgorithms/maths/NthUglyNumber.java | 3 ++- src/main/java/com/thealgorithms/misc/MedianOfMatrix.java | 2 +- .../com/thealgorithms/misc/PalindromeSinglyLinkedList.java | 5 ++--- src/main/java/com/thealgorithms/others/KochSnowflake.java | 3 ++- .../scheduling/PreemptivePriorityScheduling.java | 3 ++- .../java/com/thealgorithms/scheduling/SJFScheduling.java | 3 ++- .../scheduling/diskscheduling/SSFScheduling.java | 3 ++- src/main/java/com/thealgorithms/sorts/BucketSort.java | 2 +- src/main/java/com/thealgorithms/sorts/PatienceSort.java | 2 +- src/main/java/com/thealgorithms/sorts/PigeonholeSort.java | 2 +- src/main/java/com/thealgorithms/sorts/TreeSort.java | 2 +- src/main/java/com/thealgorithms/strings/WordLadder.java | 4 ++-- .../datastructures/graphs/BoruvkaAlgorithmTest.java | 2 +- .../datastructures/graphs/EdmondsBlossomAlgorithmTest.java | 3 ++- src/test/java/com/thealgorithms/misc/WordBoggleTest.java | 3 ++- .../scheduling/PreemptivePrioritySchedulingTest.java | 3 ++- .../java/com/thealgorithms/searches/QuickSelectTest.java | 3 ++- 28 files changed, 46 insertions(+), 33 deletions(-) diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index bfc7716730c..14bc5dfe943 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -87,9 +87,6 @@ - - - diff --git a/src/main/java/com/thealgorithms/backtracking/CrosswordSolver.java b/src/main/java/com/thealgorithms/backtracking/CrosswordSolver.java index cbd9f70f74f..6bfb026c7de 100644 --- a/src/main/java/com/thealgorithms/backtracking/CrosswordSolver.java +++ b/src/main/java/com/thealgorithms/backtracking/CrosswordSolver.java @@ -1,6 +1,7 @@ package com.thealgorithms.backtracking; import java.util.ArrayList; +import java.util.Collection; import java.util.List; /** @@ -95,7 +96,7 @@ public static void removeWord(char[][] puzzle, String word, int row, int col, bo * @param words The list of words to be placed. * @return true if the crossword is solved, false otherwise. */ - public static boolean solveCrossword(char[][] puzzle, List words) { + public static boolean solveCrossword(char[][] puzzle, Collection words) { // Create a mutable copy of the words list List remainingWords = new ArrayList<>(words); diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java b/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java index 460c05e0440..741caa59c5b 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java @@ -94,7 +94,7 @@ public int getEstimated() { } // Initializes the graph with edges defined in the input data - static void initializeGraph(Graph graph, ArrayList data) { + static void initializeGraph(Graph graph, List data) { for (int i = 0; i < data.size(); i += 4) { graph.addEdge(new Edge(data.get(i), data.get(i + 1), data.get(i + 2))); } diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithm.java index 27ad96d7187..db716580d68 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithm.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithm.java @@ -30,7 +30,7 @@ private EdmondsBlossomAlgorithm() { * @param vertexCount The number of vertices in the graph. * @return A list of matched pairs of vertices. */ - public static List maximumMatching(List edges, int vertexCount) { + public static List maximumMatching(Iterable edges, int vertexCount) { List> graph = new ArrayList<>(vertexCount); // Initialize each vertex's adjacency list. diff --git a/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedArrayList.java b/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedArrayList.java index 99ab09f81c1..e315c423633 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedArrayList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedArrayList.java @@ -1,6 +1,7 @@ package com.thealgorithms.datastructures.lists; import java.util.ArrayList; +import java.util.Collection; import java.util.List; /** @@ -38,7 +39,7 @@ public static void main(String[] args) { * @param listB the second list to merge * @param listC the result list after merging */ - public static void merge(List listA, List listB, List listC) { + public static void merge(List listA, List listB, Collection listC) { int pa = 0; /* the index of listA */ int pb = 0; diff --git a/src/main/java/com/thealgorithms/geometry/ConvexHull.java b/src/main/java/com/thealgorithms/geometry/ConvexHull.java index 19cecdc3a3a..17f400854c6 100644 --- a/src/main/java/com/thealgorithms/geometry/ConvexHull.java +++ b/src/main/java/com/thealgorithms/geometry/ConvexHull.java @@ -1,6 +1,7 @@ package com.thealgorithms.geometry; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; @@ -89,7 +90,7 @@ public static List convexHullRecursive(List points) { return result; } - private static void constructHull(List points, Point left, Point right, Set convexSet) { + private static void constructHull(Collection points, Point left, Point right, Set convexSet) { if (!points.isEmpty()) { Point extremePoint = null; int extremePointDistance = Integer.MIN_VALUE; diff --git a/src/main/java/com/thealgorithms/maths/CircularConvolutionFFT.java b/src/main/java/com/thealgorithms/maths/CircularConvolutionFFT.java index f7010acf452..87fc5af57b8 100644 --- a/src/main/java/com/thealgorithms/maths/CircularConvolutionFFT.java +++ b/src/main/java/com/thealgorithms/maths/CircularConvolutionFFT.java @@ -1,6 +1,7 @@ package com.thealgorithms.maths; import java.util.ArrayList; +import java.util.Collection; /** * Class for circular convolution of two discrete signals using the convolution @@ -19,7 +20,7 @@ private CircularConvolutionFFT() { * @param x The signal to be padded. * @param newSize The new size of the signal. */ - private static void padding(ArrayList x, int newSize) { + private static void padding(Collection x, int newSize) { if (x.size() < newSize) { int diff = newSize - x.size(); for (int i = 0; i < diff; i++) { diff --git a/src/main/java/com/thealgorithms/maths/ConvolutionFFT.java b/src/main/java/com/thealgorithms/maths/ConvolutionFFT.java index ce35b02ca13..ed1ba1bbefc 100644 --- a/src/main/java/com/thealgorithms/maths/ConvolutionFFT.java +++ b/src/main/java/com/thealgorithms/maths/ConvolutionFFT.java @@ -1,6 +1,7 @@ package com.thealgorithms.maths; import java.util.ArrayList; +import java.util.Collection; /** * Class for linear convolution of two discrete signals using the convolution @@ -19,7 +20,7 @@ private ConvolutionFFT() { * @param x The signal to be padded. * @param newSize The new size of the signal. */ - private static void padding(ArrayList x, int newSize) { + private static void padding(Collection x, int newSize) { if (x.size() < newSize) { int diff = newSize - x.size(); for (int i = 0; i < diff; i++) { diff --git a/src/main/java/com/thealgorithms/maths/FFT.java b/src/main/java/com/thealgorithms/maths/FFT.java index 7ca7543d798..47605f010b2 100644 --- a/src/main/java/com/thealgorithms/maths/FFT.java +++ b/src/main/java/com/thealgorithms/maths/FFT.java @@ -1,6 +1,7 @@ package com.thealgorithms.maths; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; /** @@ -274,7 +275,7 @@ private static int reverseBits(int num, int log2n) { * * @param x The ArrayList to be padded. */ - private static void paddingPowerOfTwo(ArrayList x) { + private static void paddingPowerOfTwo(Collection x) { int n = 1; int oldSize = x.size(); while (n < oldSize) { diff --git a/src/main/java/com/thealgorithms/maths/FFTBluestein.java b/src/main/java/com/thealgorithms/maths/FFTBluestein.java index 388de6fed3e..7a03c20cc64 100644 --- a/src/main/java/com/thealgorithms/maths/FFTBluestein.java +++ b/src/main/java/com/thealgorithms/maths/FFTBluestein.java @@ -1,6 +1,7 @@ package com.thealgorithms.maths; import java.util.ArrayList; +import java.util.List; /** * Class for calculating the Fast Fourier Transform (FFT) of a discrete signal @@ -25,7 +26,7 @@ private FFTBluestein() { * IFFT of signal x. * @param inverse True if you want to find the inverse FFT. */ - public static void fftBluestein(ArrayList x, boolean inverse) { + public static void fftBluestein(List x, boolean inverse) { int n = x.size(); int bnSize = 2 * n - 1; int direction = inverse ? -1 : 1; diff --git a/src/main/java/com/thealgorithms/maths/Gaussian.java b/src/main/java/com/thealgorithms/maths/Gaussian.java index 255a84d1385..1e02579757c 100644 --- a/src/main/java/com/thealgorithms/maths/Gaussian.java +++ b/src/main/java/com/thealgorithms/maths/Gaussian.java @@ -1,12 +1,13 @@ package com.thealgorithms.maths; import java.util.ArrayList; +import java.util.List; public final class Gaussian { private Gaussian() { } - public static ArrayList gaussian(int matSize, ArrayList matrix) { + public static ArrayList gaussian(int matSize, List matrix) { int i; int j = 0; diff --git a/src/main/java/com/thealgorithms/maths/NthUglyNumber.java b/src/main/java/com/thealgorithms/maths/NthUglyNumber.java index 90507701332..6484026c14d 100644 --- a/src/main/java/com/thealgorithms/maths/NthUglyNumber.java +++ b/src/main/java/com/thealgorithms/maths/NthUglyNumber.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Map; import org.apache.commons.lang3.tuple.MutablePair; /** @@ -64,7 +65,7 @@ private void updatePositions() { } } - private long computeCandidate(final MutablePair entry) { + private long computeCandidate(final Map.Entry entry) { return entry.getKey() * uglyNumbers.get(entry.getValue()); } diff --git a/src/main/java/com/thealgorithms/misc/MedianOfMatrix.java b/src/main/java/com/thealgorithms/misc/MedianOfMatrix.java index d4ddffe8ddd..edeedbbee54 100644 --- a/src/main/java/com/thealgorithms/misc/MedianOfMatrix.java +++ b/src/main/java/com/thealgorithms/misc/MedianOfMatrix.java @@ -13,7 +13,7 @@ public final class MedianOfMatrix { private MedianOfMatrix() { } - public static int median(List> matrix) { + public static int median(Iterable> matrix) { // Flatten the matrix into a 1D list List linear = new ArrayList<>(); for (List row : matrix) { diff --git a/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java b/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java index 8af8a9b030e..51dc099ba32 100644 --- a/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java +++ b/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java @@ -1,6 +1,5 @@ package com.thealgorithms.misc; -import com.thealgorithms.datastructures.lists.SinglyLinkedList; import java.util.Stack; /** @@ -15,8 +14,8 @@ public final class PalindromeSinglyLinkedList { private PalindromeSinglyLinkedList() { } - public static boolean isPalindrome(final SinglyLinkedList linkedList) { - Stack linkedListValues = new Stack<>(); + public static boolean isPalindrome(final Iterable linkedList) { + var linkedListValues = new Stack<>(); for (final var x : linkedList) { linkedListValues.push(x); diff --git a/src/main/java/com/thealgorithms/others/KochSnowflake.java b/src/main/java/com/thealgorithms/others/KochSnowflake.java index 0e2600a7d72..46b8edb1f17 100644 --- a/src/main/java/com/thealgorithms/others/KochSnowflake.java +++ b/src/main/java/com/thealgorithms/others/KochSnowflake.java @@ -7,6 +7,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.List; import javax.imageio.ImageIO; /** @@ -125,7 +126,7 @@ public static BufferedImage getKochSnowflake(int imageWidth, int steps) { * applied. * @return The transformed vectors after the iteration-step. */ - private static ArrayList iterationStep(ArrayList vectors) { + private static ArrayList iterationStep(List vectors) { ArrayList newVectors = new ArrayList(); for (int i = 0; i < vectors.size() - 1; i++) { Vector2 startVector = vectors.get(i); diff --git a/src/main/java/com/thealgorithms/scheduling/PreemptivePriorityScheduling.java b/src/main/java/com/thealgorithms/scheduling/PreemptivePriorityScheduling.java index 27d85a94d6f..66c99661d13 100644 --- a/src/main/java/com/thealgorithms/scheduling/PreemptivePriorityScheduling.java +++ b/src/main/java/com/thealgorithms/scheduling/PreemptivePriorityScheduling.java @@ -2,6 +2,7 @@ import com.thealgorithms.devutils.entities.ProcessDetails; import java.util.ArrayList; +import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.PriorityQueue; @@ -15,7 +16,7 @@ public class PreemptivePriorityScheduling { protected final List processes; protected final List ganttChart; - public PreemptivePriorityScheduling(List processes) { + public PreemptivePriorityScheduling(Collection processes) { this.processes = new ArrayList<>(processes); this.ganttChart = new ArrayList<>(); } diff --git a/src/main/java/com/thealgorithms/scheduling/SJFScheduling.java b/src/main/java/com/thealgorithms/scheduling/SJFScheduling.java index ca2144e4924..6d105003e68 100644 --- a/src/main/java/com/thealgorithms/scheduling/SJFScheduling.java +++ b/src/main/java/com/thealgorithms/scheduling/SJFScheduling.java @@ -2,6 +2,7 @@ import com.thealgorithms.devutils.entities.ProcessDetails; import java.util.ArrayList; +import java.util.List; /** * Implementation of Shortest Job First Algorithm: The algorithm allows the waiting process with the @@ -87,7 +88,7 @@ public void scheduleProcesses() { * @return returns the process' with the shortest burst time OR NULL if there are no ready * processes */ - private ProcessDetails findShortestJob(ArrayList readyProcesses) { + private ProcessDetails findShortestJob(List readyProcesses) { if (readyProcesses.isEmpty()) { return null; } diff --git a/src/main/java/com/thealgorithms/scheduling/diskscheduling/SSFScheduling.java b/src/main/java/com/thealgorithms/scheduling/diskscheduling/SSFScheduling.java index 30838821a2d..261c1a38839 100644 --- a/src/main/java/com/thealgorithms/scheduling/diskscheduling/SSFScheduling.java +++ b/src/main/java/com/thealgorithms/scheduling/diskscheduling/SSFScheduling.java @@ -1,6 +1,7 @@ package com.thealgorithms.scheduling.diskscheduling; import java.util.ArrayList; +import java.util.Collection; import java.util.List; /** @@ -24,7 +25,7 @@ public SSFScheduling(int currentPosition) { this.currentPosition = currentPosition; } - public List execute(List requests) { + public List execute(Collection requests) { List result = new ArrayList<>(requests); List orderedRequests = new ArrayList<>(); diff --git a/src/main/java/com/thealgorithms/sorts/BucketSort.java b/src/main/java/com/thealgorithms/sorts/BucketSort.java index a6901ac339a..62c5e929593 100644 --- a/src/main/java/com/thealgorithms/sorts/BucketSort.java +++ b/src/main/java/com/thealgorithms/sorts/BucketSort.java @@ -79,7 +79,7 @@ private > void distributeElementsIntoBuckets(T[] array, * @param the type of elements in the array * @return the sorted array */ - private > T[] concatenateBuckets(List> buckets, T[] array) { + private > T[] concatenateBuckets(Iterable> buckets, T[] array) { int index = 0; for (List bucket : buckets) { Collections.sort(bucket); diff --git a/src/main/java/com/thealgorithms/sorts/PatienceSort.java b/src/main/java/com/thealgorithms/sorts/PatienceSort.java index 52ed30d586b..0edce8d9a15 100644 --- a/src/main/java/com/thealgorithms/sorts/PatienceSort.java +++ b/src/main/java/com/thealgorithms/sorts/PatienceSort.java @@ -72,7 +72,7 @@ private static > List> formPiles(final T[] array * @param the type of elements in the piles, must be comparable * @return a priority queue containing the top element of each pile */ - private static > PriorityQueue> mergePiles(final List> piles) { + private static > PriorityQueue> mergePiles(final Iterable> piles) { PriorityQueue> pq = new PriorityQueue<>(); for (List pile : piles) { pq.add(new PileNode<>(pile.removeLast(), pile)); diff --git a/src/main/java/com/thealgorithms/sorts/PigeonholeSort.java b/src/main/java/com/thealgorithms/sorts/PigeonholeSort.java index 78d7d81d709..19f4291d821 100644 --- a/src/main/java/com/thealgorithms/sorts/PigeonholeSort.java +++ b/src/main/java/com/thealgorithms/sorts/PigeonholeSort.java @@ -78,7 +78,7 @@ private static void populatePigeonHoles(int[] array, List> pigeonH * @param array the array to be sorted * @param pigeonHoles the populated pigeonholes */ - private static void collectFromPigeonHoles(int[] array, List> pigeonHoles) { + private static void collectFromPigeonHoles(int[] array, Iterable> pigeonHoles) { int index = 0; for (final var pigeonHole : pigeonHoles) { for (final int element : pigeonHole) { diff --git a/src/main/java/com/thealgorithms/sorts/TreeSort.java b/src/main/java/com/thealgorithms/sorts/TreeSort.java index e060af542f9..6f4e5509489 100644 --- a/src/main/java/com/thealgorithms/sorts/TreeSort.java +++ b/src/main/java/com/thealgorithms/sorts/TreeSort.java @@ -51,7 +51,7 @@ private > T[] doTreeSortArray(T[] unsortedArray) { return unsortedArray; } - private > List doTreeSortList(List unsortedList) { + private > List doTreeSortList(Iterable unsortedList) { // create a generic BST tree BSTRecursiveGeneric tree = new BSTRecursiveGeneric(); diff --git a/src/main/java/com/thealgorithms/strings/WordLadder.java b/src/main/java/com/thealgorithms/strings/WordLadder.java index 084a682b04a..665e5ff3220 100644 --- a/src/main/java/com/thealgorithms/strings/WordLadder.java +++ b/src/main/java/com/thealgorithms/strings/WordLadder.java @@ -1,8 +1,8 @@ package com.thealgorithms.strings; +import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; -import java.util.List; import java.util.Queue; import java.util.Set; @@ -22,7 +22,7 @@ private WordLadder() { * @param wordList a list of words that can be used in the transformation sequence * @return the number of words in the shortest transformation sequence, or 0 if no such sequence exists */ - public static int ladderLength(String beginWord, String endWord, List wordList) { + public static int ladderLength(String beginWord, String endWord, Collection wordList) { Set wordSet = new HashSet<>(wordList); if (!wordSet.contains(endWord)) { diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/BoruvkaAlgorithmTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/BoruvkaAlgorithmTest.java index 8cd0b0a6838..f089169903d 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/BoruvkaAlgorithmTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/BoruvkaAlgorithmTest.java @@ -183,7 +183,7 @@ void testEdgesRange() { * @param result list of edges in the Minimum Spanning Tree * @return the total weight of the Minimum Spanning Tree */ - int computeTotalWeight(final List result) { + int computeTotalWeight(final Iterable result) { int totalWeight = 0; for (final var edge : result) { totalWeight += edge.weight; diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithmTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithmTest.java index 4a7232447e5..aa8e6beeb3d 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithmTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithmTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; import org.junit.jupiter.api.Test; @@ -25,7 +26,7 @@ public class EdmondsBlossomAlgorithmTest { * @param matching List of matched pairs returned by the algorithm. * @return A sorted 2D array of matching pairs. */ - private int[][] convertMatchingToArray(List matching) { + private int[][] convertMatchingToArray(Collection matching) { // Convert the list of pairs into an array int[][] result = matching.toArray(new int[0][]); diff --git a/src/test/java/com/thealgorithms/misc/WordBoggleTest.java b/src/test/java/com/thealgorithms/misc/WordBoggleTest.java index 2c79ec79656..1d4ed7c5e73 100644 --- a/src/test/java/com/thealgorithms/misc/WordBoggleTest.java +++ b/src/test/java/com/thealgorithms/misc/WordBoggleTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; @@ -44,7 +45,7 @@ private static Stream provideTestCases() { @ParameterizedTest @MethodSource("provideSpecialCases") - void testBoggleBoardSpecialCases(char[][] specialBoard, String[] words, List expectedWords, String testDescription) { + void testBoggleBoardSpecialCases(char[][] specialBoard, String[] words, Collection expectedWords, String testDescription) { List result = WordBoggle.boggleBoard(specialBoard, words); assertEquals(expectedWords.size(), result.size(), "Test failed for: " + testDescription); assertTrue(expectedWords.containsAll(result), "Test failed for: " + testDescription); diff --git a/src/test/java/com/thealgorithms/scheduling/PreemptivePrioritySchedulingTest.java b/src/test/java/com/thealgorithms/scheduling/PreemptivePrioritySchedulingTest.java index d0005dda909..ea692686afd 100644 --- a/src/test/java/com/thealgorithms/scheduling/PreemptivePrioritySchedulingTest.java +++ b/src/test/java/com/thealgorithms/scheduling/PreemptivePrioritySchedulingTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import com.thealgorithms.devutils.entities.ProcessDetails; +import java.util.Collection; import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; @@ -17,7 +18,7 @@ class PreemptivePrioritySchedulingTest { @ParameterizedTest @MethodSource("provideProcessesAndExpectedSchedules") - void testPreemptivePriorityScheduling(List processes, List expectedSchedule) { + void testPreemptivePriorityScheduling(Collection processes, List expectedSchedule) { PreemptivePriorityScheduling scheduler = new PreemptivePriorityScheduling(processes); scheduler.scheduleProcesses(); assertEquals(expectedSchedule, scheduler.ganttChart); diff --git a/src/test/java/com/thealgorithms/searches/QuickSelectTest.java b/src/test/java/com/thealgorithms/searches/QuickSelectTest.java index dd04c85b76a..cf160b0ff4b 100644 --- a/src/test/java/com/thealgorithms/searches/QuickSelectTest.java +++ b/src/test/java/com/thealgorithms/searches/QuickSelectTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -225,7 +226,7 @@ private static List generateRandomCharacters(int n) { return RANDOM.ints(n, ASCII_A, ASCII_Z).mapToObj(i -> (char) i).collect(Collectors.toList()); } - private static > List getSortedCopyOfList(List list) { + private static > List getSortedCopyOfList(Collection list) { return list.stream().sorted().collect(Collectors.toList()); } } From 30504c179ebc42d15c7b738435a87a3d8bc4053b Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:06:22 +0530 Subject: [PATCH 08/75] Add `MinStackUsingSingleStack` algorithm (#5759) --- DIRECTORY.md | 4 ++ .../stacks/MinStackUsingSingleStack.java | 65 ++++++++++++++++++ .../stacks/MinStackUsingSingleStackTest.java | 66 +++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 src/main/java/com/thealgorithms/stacks/MinStackUsingSingleStack.java create mode 100644 src/test/java/com/thealgorithms/stacks/MinStackUsingSingleStackTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 265d1aeeb89..6073904a745 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -431,6 +431,7 @@ * [StrobogrammaticNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/StrobogrammaticNumber.java) * [SumOfArithmeticSeries](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SumOfArithmeticSeries.java) * [SumOfDigits](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SumOfDigits.java) + * [SumOfOddNumbers](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SumOfOddNumbers.java) * [SumWithoutArithmeticOperators](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SumWithoutArithmeticOperators.java) * [TrinomialTriangle](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/TrinomialTriangle.java) * [TwinPrime](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/TwinPrime.java) @@ -612,6 +613,7 @@ * [InfixToPrefix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/InfixToPrefix.java) * [LargestRectangle](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/LargestRectangle.java) * [MaximumMinimumWindow](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/MaximumMinimumWindow.java) + * [MinStackUsingSingleStack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/MinStackUsingSingleStack.java) * [NextGreaterElement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/NextGreaterElement.java) * [NextSmallerElement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/NextSmallerElement.java) * [PostfixEvaluator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/PostfixEvaluator.java) @@ -1009,6 +1011,7 @@ * [StrobogrammaticNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/StrobogrammaticNumberTest.java) * [SumOfArithmeticSeriesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SumOfArithmeticSeriesTest.java) * [SumOfDigitsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SumOfDigitsTest.java) + * [SumOfOddNumbersTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SumOfOddNumbersTest.java) * [SumWithoutArithmeticOperatorsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SumWithoutArithmeticOperatorsTest.java) * [TestArmstrong](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/TestArmstrong.java) * [TwinPrimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/TwinPrimeTest.java) @@ -1164,6 +1167,7 @@ * [InfixToPostfixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/InfixToPostfixTest.java) * [InfixToPrefixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/InfixToPrefixTest.java) * [LargestRectangleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/LargestRectangleTest.java) + * [MinStackUsingSingleStackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/MinStackUsingSingleStackTest.java) * [NextGreaterElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/NextGreaterElementTest.java) * [NextSmallerElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/NextSmallerElementTest.java) * [PostfixEvaluatorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/PostfixEvaluatorTest.java) diff --git a/src/main/java/com/thealgorithms/stacks/MinStackUsingSingleStack.java b/src/main/java/com/thealgorithms/stacks/MinStackUsingSingleStack.java new file mode 100644 index 00000000000..f5e526b102c --- /dev/null +++ b/src/main/java/com/thealgorithms/stacks/MinStackUsingSingleStack.java @@ -0,0 +1,65 @@ +package com.thealgorithms.stacks; + +import java.util.EmptyStackException; +import java.util.Stack; + +/** + * Min-Stack implementation using a single stack. + * + * This stack supports push, pop, and retrieving the minimum element + * in constant time (O(1)) using a modified approach where the stack + * stores both the element and the minimum value so far. + * + * @author Hardvan + */ +public class MinStackUsingSingleStack { + private final Stack stack = new Stack<>(); + + /** + * Pushes a new value onto the stack. + * Each entry stores both the value and the minimum value so far. + * + * @param value The value to be pushed onto the stack. + */ + public void push(int value) { + if (stack.isEmpty()) { + stack.push(new long[] {value, value}); + } else { + long minSoFar = Math.min(value, stack.peek()[1]); + stack.push(new long[] {value, minSoFar}); + } + } + + /** + * Removes the top element from the stack. + */ + public void pop() { + if (!stack.isEmpty()) { + stack.pop(); + } + } + + /** + * Retrieves the top element from the stack. + * + * @return The top element of the stack. + */ + public int top() { + if (!stack.isEmpty()) { + return (int) stack.peek()[0]; + } + throw new EmptyStackException(); + } + + /** + * Retrieves the minimum element in the stack. + * + * @return The minimum element so far. + */ + public int getMin() { + if (!stack.isEmpty()) { + return (int) stack.peek()[1]; + } + throw new EmptyStackException(); + } +} diff --git a/src/test/java/com/thealgorithms/stacks/MinStackUsingSingleStackTest.java b/src/test/java/com/thealgorithms/stacks/MinStackUsingSingleStackTest.java new file mode 100644 index 00000000000..90887294638 --- /dev/null +++ b/src/test/java/com/thealgorithms/stacks/MinStackUsingSingleStackTest.java @@ -0,0 +1,66 @@ +package com.thealgorithms.stacks; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.EmptyStackException; +import org.junit.jupiter.api.Test; + +public class MinStackUsingSingleStackTest { + + @Test + public void testBasicOperations() { + MinStackUsingSingleStack minStack = new MinStackUsingSingleStack(); + + minStack.push(3); + minStack.push(5); + assertEquals(3, minStack.getMin(), "Minimum should be 3"); + + minStack.push(2); + minStack.push(1); + assertEquals(1, minStack.getMin(), "Minimum should be 1"); + + minStack.pop(); + assertEquals(2, minStack.getMin(), "Minimum should be 2"); + + minStack.pop(); + assertEquals(3, minStack.getMin(), "Minimum should be 3"); + } + + @Test + public void testTopElement() { + MinStackUsingSingleStack minStack = new MinStackUsingSingleStack(); + + minStack.push(8); + minStack.push(10); + assertEquals(10, minStack.top(), "Top element should be 10"); + + minStack.pop(); + assertEquals(8, minStack.top(), "Top element should be 8"); + } + + @Test + public void testGetMinAfterPops() { + MinStackUsingSingleStack minStack = new MinStackUsingSingleStack(); + + minStack.push(5); + minStack.push(3); + minStack.push(7); + + assertEquals(3, minStack.getMin(), "Minimum should be 3"); + + minStack.pop(); // Popping 7 + assertEquals(3, minStack.getMin(), "Minimum should still be 3"); + + minStack.pop(); // Popping 3 + assertEquals(5, minStack.getMin(), "Minimum should now be 5"); + } + + @Test + public void testEmptyStack() { + MinStackUsingSingleStack minStack = new MinStackUsingSingleStack(); + + assertThrows(EmptyStackException.class, minStack::top, "Should throw exception on top()"); + assertThrows(EmptyStackException.class, minStack::getMin, "Should throw exception on getMin()"); + } +} From 776946e165ce659cbe334f653c4e8f480f36f9fd Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Tue, 15 Oct 2024 13:42:50 +0530 Subject: [PATCH 09/75] feat: Add `MinStackUsingTwoStacks` new algorithm with Junit tests (#5758) --- DIRECTORY.md | 2 + .../stacks/MinStackUsingTwoStacks.java | 57 +++++++++++++++++++ .../stacks/MinStackUsingTwoStacksTest.java | 38 +++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 src/main/java/com/thealgorithms/stacks/MinStackUsingTwoStacks.java create mode 100644 src/test/java/com/thealgorithms/stacks/MinStackUsingTwoStacksTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 6073904a745..cb26c292b5e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -614,6 +614,7 @@ * [LargestRectangle](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/LargestRectangle.java) * [MaximumMinimumWindow](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/MaximumMinimumWindow.java) * [MinStackUsingSingleStack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/MinStackUsingSingleStack.java) + * [MinStackUsingTwoStacks](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/MinStackUsingTwoStacks.java) * [NextGreaterElement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/NextGreaterElement.java) * [NextSmallerElement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/NextSmallerElement.java) * [PostfixEvaluator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/PostfixEvaluator.java) @@ -1168,6 +1169,7 @@ * [InfixToPrefixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/InfixToPrefixTest.java) * [LargestRectangleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/LargestRectangleTest.java) * [MinStackUsingSingleStackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/MinStackUsingSingleStackTest.java) + * [MinStackUsingTwoStacksTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/MinStackUsingTwoStacksTest.java) * [NextGreaterElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/NextGreaterElementTest.java) * [NextSmallerElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/NextSmallerElementTest.java) * [PostfixEvaluatorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/PostfixEvaluatorTest.java) diff --git a/src/main/java/com/thealgorithms/stacks/MinStackUsingTwoStacks.java b/src/main/java/com/thealgorithms/stacks/MinStackUsingTwoStacks.java new file mode 100644 index 00000000000..47e337c80b5 --- /dev/null +++ b/src/main/java/com/thealgorithms/stacks/MinStackUsingTwoStacks.java @@ -0,0 +1,57 @@ +package com.thealgorithms.stacks; + +import java.util.Stack; + +/** + * Min-Stack implementation that supports push, pop, and retrieving the minimum element in constant time. + * + * @author Hardvan + */ +public final class MinStackUsingTwoStacks { + MinStackUsingTwoStacks() { + } + + private final Stack stack = new Stack<>(); + private final Stack minStack = new Stack<>(); + + /** + * Pushes a new element onto the {@code stack}. + * If the value is less than or equal to the current minimum, it is also pushed onto the {@code minStack}. + * + * @param value The value to be pushed. + */ + public void push(int value) { + stack.push(value); + if (minStack.isEmpty() || value <= minStack.peek()) { + minStack.push(value); + } + } + + /** + * Removes the top element from the stack. + * If the element is the minimum element, it is also removed from the {@code minStack}. + */ + public void pop() { + if (stack.pop().equals(minStack.peek())) { + minStack.pop(); + } + } + + /** + * Retrieves the top element of the stack. + * + * @return The top element. + */ + public int top() { + return stack.peek(); + } + + /** + * Retrieves the minimum element in the stack. + * + * @return The minimum element. + */ + public int getMin() { + return minStack.peek(); + } +} diff --git a/src/test/java/com/thealgorithms/stacks/MinStackUsingTwoStacksTest.java b/src/test/java/com/thealgorithms/stacks/MinStackUsingTwoStacksTest.java new file mode 100644 index 00000000000..e5deb17e9a8 --- /dev/null +++ b/src/test/java/com/thealgorithms/stacks/MinStackUsingTwoStacksTest.java @@ -0,0 +1,38 @@ +package com.thealgorithms.stacks; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class MinStackUsingTwoStacksTest { + + @Test + public void testMinStackOperations() { + MinStackUsingTwoStacks minStack = new MinStackUsingTwoStacks(); + minStack.push(3); + minStack.push(5); + assertEquals(3, minStack.getMin()); + + minStack.push(2); + minStack.push(1); + assertEquals(1, minStack.getMin()); + + minStack.pop(); + assertEquals(2, minStack.getMin()); + } + + @Test + public void testMinStackOperations2() { + MinStackUsingTwoStacks minStack = new MinStackUsingTwoStacks(); + minStack.push(3); + minStack.push(5); + assertEquals(3, minStack.getMin()); + + minStack.push(2); + minStack.push(1); + assertEquals(1, minStack.getMin()); + + minStack.pop(); + assertEquals(2, minStack.getMin()); + } +} From adf21ab0c87bffd1c774072335a7d9d4f62fcee4 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:18:48 +0530 Subject: [PATCH 10/75] feat: Add `CelebrityFinder` new algorithm with Junit tests (#5756) --- DIRECTORY.md | 2 + .../thealgorithms/stacks/CelebrityFinder.java | 52 +++++++++++++++++++ .../stacks/CelebrityFinderTest.java | 41 +++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 src/main/java/com/thealgorithms/stacks/CelebrityFinder.java create mode 100644 src/test/java/com/thealgorithms/stacks/CelebrityFinderTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index cb26c292b5e..11ce5b7440a 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -607,6 +607,7 @@ * [WiggleSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/WiggleSort.java) * stacks * [BalancedBrackets](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/BalancedBrackets.java) + * [CelebrityFinder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/CelebrityFinder.java) * [DecimalToAnyUsingStack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/DecimalToAnyUsingStack.java) * [DuplicateBrackets](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/DuplicateBrackets.java) * [InfixToPostfix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/InfixToPostfix.java) @@ -1163,6 +1164,7 @@ * [WiggleSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/WiggleSortTest.java) * stacks * [BalancedBracketsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/BalancedBracketsTest.java) + * [CelebrityFinderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/CelebrityFinderTest.java) * [DecimalToAnyUsingStackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/DecimalToAnyUsingStackTest.java) * [DuplicateBracketsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/DuplicateBracketsTest.java) * [InfixToPostfixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/InfixToPostfixTest.java) diff --git a/src/main/java/com/thealgorithms/stacks/CelebrityFinder.java b/src/main/java/com/thealgorithms/stacks/CelebrityFinder.java new file mode 100644 index 00000000000..67ac861ef82 --- /dev/null +++ b/src/main/java/com/thealgorithms/stacks/CelebrityFinder.java @@ -0,0 +1,52 @@ +package com.thealgorithms.stacks; + +import java.util.Stack; + +/** + * Solves the celebrity problem using a stack-based algorithm. + * + *

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

Applications: Graph theory and social network analysis. + * + * @author Hardvan + */ +public final class CelebrityFinder { + private CelebrityFinder() { + } + + /** + * Finds the celebrity in the given party matrix using a stack-based algorithm. + * + * @param party A 2D matrix where party[i][j] is 1 if i knows j, otherwise 0. + * @return The index of the celebrity, or -1 if there is no celebrity. + */ + public static int findCelebrity(int[][] party) { + + // Push all people onto the stack + Stack stack = new Stack<>(); + for (int i = 0; i < party.length; i++) { + stack.push(i); + } + + // Find the potential celebrity by comparing pairs + while (stack.size() > 1) { + int person1 = stack.pop(); + int person2 = stack.pop(); + + if (party[person1][person2] == 1) { + stack.push(person2); // person1 knows person2, so person2 might be the celebrity + } else { + stack.push(person1); // person1 doesn't know person2, so person1 might be the celebrity + } + } + + // Verify the candidate + int candidate = stack.pop(); + for (int i = 0; i < party.length; i++) { + if (i != candidate && (party[candidate][i] == 1 || party[i][candidate] == 0)) { + return -1; + } + } + return candidate; + } +} diff --git a/src/test/java/com/thealgorithms/stacks/CelebrityFinderTest.java b/src/test/java/com/thealgorithms/stacks/CelebrityFinderTest.java new file mode 100644 index 00000000000..da0217940c2 --- /dev/null +++ b/src/test/java/com/thealgorithms/stacks/CelebrityFinderTest.java @@ -0,0 +1,41 @@ +package com.thealgorithms.stacks; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class CelebrityFinderTest { + + @ParameterizedTest + @MethodSource("providePartyMatrices") + public void testCelebrityFinder(int[][] party, int expected) { + assertEquals(expected, CelebrityFinder.findCelebrity(party)); + } + + private static Stream providePartyMatrices() { + return Stream.of( + // Test case 1: Celebrity exists + Arguments.of(new int[][] {{0, 1, 1}, {0, 0, 1}, {0, 0, 0}}, 2), + + // Test case 2: No celebrity + Arguments.of(new int[][] {{0, 1, 0}, {1, 0, 1}, {1, 1, 0}}, -1), + + // Test case 3: Everyone knows each other, no celebrity + Arguments.of(new int[][] {{0, 1, 1}, {1, 0, 1}, {1, 1, 0}}, -1), + + // Test case 4: Single person, they are trivially a celebrity + Arguments.of(new int[][] {{0}}, 0), + + // Test case 5: All know the last person, and they know no one + Arguments.of(new int[][] {{0, 1, 1, 1}, {0, 0, 1, 1}, {0, 0, 0, 1}, {0, 0, 0, 0}}, 3), + + // Test case 6: Larger party with no celebrity + Arguments.of(new int[][] {{0, 1, 1, 0}, {1, 0, 0, 1}, {0, 1, 0, 1}, {1, 1, 1, 0}}, -1), + + // Test case 7: Celebrity at the start of the matrix + Arguments.of(new int[][] {{0, 0, 0}, {1, 0, 1}, {1, 1, 0}}, 0)); + } +} From 32bf532133bc83ae495557ab0e6b3e4c7eab4196 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:29:16 +0530 Subject: [PATCH 11/75] refactor: Enhance docs, add more tests in `ArrayCombination` (#5841) --- .../backtracking/ArrayCombination.java | 28 +++++++++++++------ .../backtracking/ArrayCombinationTest.java | 8 ++++-- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java b/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java index a064decc0eb..6569896bd1b 100644 --- a/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java +++ b/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java @@ -4,22 +4,24 @@ import java.util.List; /** - * Finds all combinations of 0...n-1 of length k + * This class provides methods to find all combinations of integers from 0 to n-1 + * of a specified length k using backtracking. */ public final class ArrayCombination { private ArrayCombination() { } /** - * Finds all combinations of length k of 0..n-1 using backtracking. + * Generates all possible combinations of length k from the integers 0 to n-1. * - * @param n Number of the elements. - * @param k Length of the combination. - * @return A list of all combinations of length k. + * @param n The total number of elements (0 to n-1). + * @param k The desired length of each combination. + * @return A list containing all combinations of length k. + * @throws IllegalArgumentException if n or k are negative, or if k is greater than n. */ public static List> combination(int n, int k) { if (n < 0 || k < 0 || k > n) { - throw new IllegalArgumentException("Wrong input."); + throw new IllegalArgumentException("Invalid input: n must be non-negative, k must be non-negative and less than or equal to n."); } List> combinations = new ArrayList<>(); @@ -27,9 +29,19 @@ public static List> combination(int n, int k) { return combinations; } + /** + * A helper method that uses backtracking to find combinations. + * + * @param combinations The list to store all valid combinations found. + * @param current The current combination being built. + * @param start The starting index for the current recursion. + * @param n The total number of elements (0 to n-1). + * @param k The desired length of each combination. + */ private static void combine(List> combinations, List current, int start, int n, int k) { - if (current.size() == k) { // Base case: combination found - combinations.add(new ArrayList<>(current)); // Copy to avoid modification + // Base case: combination found + if (current.size() == k) { + combinations.add(new ArrayList<>(current)); return; } diff --git a/src/test/java/com/thealgorithms/backtracking/ArrayCombinationTest.java b/src/test/java/com/thealgorithms/backtracking/ArrayCombinationTest.java index a4ff7fe892d..a6a3714cb59 100644 --- a/src/test/java/com/thealgorithms/backtracking/ArrayCombinationTest.java +++ b/src/test/java/com/thealgorithms/backtracking/ArrayCombinationTest.java @@ -27,10 +27,14 @@ void testCombinationThrows(int n, int k) { private static Stream regularInputs() { return Stream.of(Arguments.of(0, 0, List.of(new ArrayList())), Arguments.of(1, 0, List.of(new ArrayList())), Arguments.of(1, 1, List.of(List.of(0))), Arguments.of(3, 0, List.of(new ArrayList())), Arguments.of(3, 1, List.of(List.of(0), List.of(1), List.of(2))), - Arguments.of(4, 2, List.of(List.of(0, 1), List.of(0, 2), List.of(0, 3), List.of(1, 2), List.of(1, 3), List.of(2, 3)))); + Arguments.of(4, 2, List.of(List.of(0, 1), List.of(0, 2), List.of(0, 3), List.of(1, 2), List.of(1, 3), List.of(2, 3))), + Arguments.of(5, 3, List.of(List.of(0, 1, 2), List.of(0, 1, 3), List.of(0, 1, 4), List.of(0, 2, 3), List.of(0, 2, 4), List.of(0, 3, 4), List.of(1, 2, 3), List.of(1, 2, 4), List.of(1, 3, 4), List.of(2, 3, 4))), + Arguments.of(6, 4, + List.of(List.of(0, 1, 2, 3), List.of(0, 1, 2, 4), List.of(0, 1, 2, 5), List.of(0, 1, 3, 4), List.of(0, 1, 3, 5), List.of(0, 1, 4, 5), List.of(0, 2, 3, 4), List.of(0, 2, 3, 5), List.of(0, 2, 4, 5), List.of(0, 3, 4, 5), List.of(1, 2, 3, 4), List.of(1, 2, 3, 5), List.of(1, 2, 4, 5), + List.of(1, 3, 4, 5), List.of(2, 3, 4, 5)))); } private static Stream wrongInputs() { - return Stream.of(Arguments.of(-1, 0), Arguments.of(0, -1), Arguments.of(2, 100)); + return Stream.of(Arguments.of(-1, 0), Arguments.of(0, -1), Arguments.of(2, 100), Arguments.of(3, 4)); } } From be70801aa2fde9d9f183a3ae8d7f787fbc372992 Mon Sep 17 00:00:00 2001 From: Saahil Mahato <115351000+saahil-mahato@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:57:58 +0545 Subject: [PATCH 12/75] feat: add bresenham's line drawing algorithm (#5779) --- .../thealgorithms/geometry/BresenhamLine.java | 69 +++++++++++++++++++ .../geometry/BresenhamLineTest.java | 57 +++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 src/main/java/com/thealgorithms/geometry/BresenhamLine.java create mode 100644 src/test/java/com/thealgorithms/geometry/BresenhamLineTest.java diff --git a/src/main/java/com/thealgorithms/geometry/BresenhamLine.java b/src/main/java/com/thealgorithms/geometry/BresenhamLine.java new file mode 100644 index 00000000000..51d9930c025 --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/BresenhamLine.java @@ -0,0 +1,69 @@ +package com.thealgorithms.geometry; + +import java.awt.Point; +import java.util.ArrayList; +import java.util.List; + +/** + * The {@code BresenhamLine} class implements the Bresenham's line algorithm, + * which is an efficient way to determine the points of a straight line + * between two given points in a 2D space. + * + *

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

+ * + * For more information, please visit {@link https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm} + */ +public final class BresenhamLine { + + private BresenhamLine() { + // Private constructor to prevent instantiation. + } + + /** + * Finds the list of points that form a straight line between two endpoints. + * + * @param x0 the x-coordinate of the starting point + * @param y0 the y-coordinate of the starting point + * @param x1 the x-coordinate of the ending point + * @param y1 the y-coordinate of the ending point + * @return a {@code List} containing all points on the line + */ + public static List findLine(int x0, int y0, int x1, int y1) { + List line = new ArrayList<>(); + + // Calculate differences and steps for each axis + int dx = Math.abs(x1 - x0); // Change in x + int dy = Math.abs(y1 - y0); // Change in y + int sx = (x0 < x1) ? 1 : -1; // Step in x direction + int sy = (y0 < y1) ? 1 : -1; // Step in y direction + int err = dx - dy; // Initial error term + + // Loop until we reach the endpoint + while (true) { + line.add(new Point(x0, y0)); // Add current point to the line + + // Check if we've reached the endpoint + if (x0 == x1 && y0 == y1) { + break; // Exit loop if endpoint is reached + } + + // Calculate error term doubled for decision making + final int e2 = err * 2; + + // Adjust x coordinate if necessary + if (e2 > -dy) { + err -= dy; // Update error term + x0 += sx; // Move to next point in x direction + } + + // Adjust y coordinate if necessary + if (e2 < dx) { + err += dx; // Update error term + y0 += sy; // Move to next point in y direction + } + } + + return line; // Return the list of points forming the line + } +} diff --git a/src/test/java/com/thealgorithms/geometry/BresenhamLineTest.java b/src/test/java/com/thealgorithms/geometry/BresenhamLineTest.java new file mode 100644 index 00000000000..9df308497dd --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/BresenhamLineTest.java @@ -0,0 +1,57 @@ +package com.thealgorithms.geometry; + +import java.awt.Point; +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * The {@code BresenhamLineTest} class contains unit tests for the + * {@code BresenhamLine} class, specifically testing the + * {@code findLine} method. + * + *

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

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

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

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

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

+ * + * @param x0 the x-coordinate of the starting point + * @param y0 the y-coordinate of the starting point + * @param x1 the x-coordinate of the ending point + * @param y1 the y-coordinate of the ending point + * @param expected a collection of expected points that should form a line + */ + @ParameterizedTest + @MethodSource("linePointsProvider") + void testFindLine(int x0, int y0, int x1, int y1, Collection expected) { + List actual = BresenhamLine.findLine(x0, y0, x1, y1); + Assertions.assertEquals(expected.size(), actual.size(), "The size of the points list should match."); + Assertions.assertTrue(expected.containsAll(actual) && actual.containsAll(expected), "The points generated should match the expected points."); + } +} From 9f5478f5995a8d9523bb1dea19c6a191bc2f74d6 Mon Sep 17 00:00:00 2001 From: Radhika Shah <33092356+radhikashah0499@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:56:10 +0530 Subject: [PATCH 13/75] Fix a wrong comment in the tree postorder traversal (#5774) --- .../java/com/thealgorithms/datastructures/trees/BinaryTree.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/datastructures/trees/BinaryTree.java b/src/main/java/com/thealgorithms/datastructures/trees/BinaryTree.java index d4d677a4cda..cf0de4a9203 100644 --- a/src/main/java/com/thealgorithms/datastructures/trees/BinaryTree.java +++ b/src/main/java/com/thealgorithms/datastructures/trees/BinaryTree.java @@ -281,7 +281,7 @@ public void preOrder(Node localRoot) { } /** - * Prints rightChild - leftChild - root + * Prints leftChild - rightChild - root * * @param localRoot The local root of the binary tree */ From 640d82358072106285f1ec952b0b671742dfd7bf Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:59:37 +0530 Subject: [PATCH 14/75] Add tests, remove main in PalindromePrime (#5773) --- DIRECTORY.md | 3 ++ .../thealgorithms/misc/PalindromePrime.java | 52 ++++++++++-------- .../misc/PalindromePrimeTest.java | 53 +++++++++++++++++++ 3 files changed, 85 insertions(+), 23 deletions(-) create mode 100644 src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 11ce5b7440a..d0de70715bb 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -304,6 +304,7 @@ * [WildcardMatching](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/WildcardMatching.java) * [WineProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java) * geometry + * [BresenhamLine](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/BresenhamLine.java) * [ConvexHull](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/ConvexHull.java) * [GrahamScan](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/GrahamScan.java) * [Point](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/Point.java) @@ -902,6 +903,7 @@ * [WildcardMatchingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/WildcardMatchingTest.java) * [WineProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java) * geometry + * [BresenhamLineTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/BresenhamLineTest.java) * [ConvexHullTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/ConvexHullTest.java) * [GrahamScanTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/GrahamScanTest.java) * greedyalgorithms @@ -1026,6 +1028,7 @@ * [MedianOfMatrixtest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/MedianOfMatrixtest.java) * [MedianOfRunningArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/MedianOfRunningArrayTest.java) * [MirrorOfMatrixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/MirrorOfMatrixTest.java) + * [PalindromePrimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java) * [PalindromeSinglyLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java) * [RangeInSortedArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java) * [ThreeSumProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/ThreeSumProblemTest.java) diff --git a/src/main/java/com/thealgorithms/misc/PalindromePrime.java b/src/main/java/com/thealgorithms/misc/PalindromePrime.java index e1cbf3ff839..164e957a9d1 100644 --- a/src/main/java/com/thealgorithms/misc/PalindromePrime.java +++ b/src/main/java/com/thealgorithms/misc/PalindromePrime.java @@ -1,51 +1,57 @@ package com.thealgorithms.misc; -import java.util.Scanner; +import java.util.ArrayList; +import java.util.List; public final class PalindromePrime { private PalindromePrime() { } - public static void main(String[] args) { // Main function - Scanner in = new Scanner(System.in); - System.out.println("Enter the quantity of First Palindromic Primes you want"); - int n = in.nextInt(); // Input of how many first palindromic prime we want - functioning(n); // calling function - functioning - in.close(); - } + public static boolean prime(int num) { + if (num < 2) { + return false; // Handle edge case for numbers < 2 + } + if (num == 2) { + return true; // 2 is prime + } + if (num % 2 == 0) { + return false; // Even numbers > 2 are not prime + } - public static boolean prime(int num) { // checking if number is prime or not for (int divisor = 3; divisor <= Math.sqrt(num); divisor += 2) { if (num % divisor == 0) { - return false; // false if not prime + return false; } } - return true; // True if prime + return true; } - public static int reverse(int n) { // Returns the reverse of the number + public static int reverse(int n) { int reverse = 0; while (n != 0) { - reverse *= 10; - reverse += n % 10; + reverse = reverse * 10 + (n % 10); n /= 10; } return reverse; } - public static void functioning(int y) { - if (y == 0) { - return; + public static List generatePalindromePrimes(int n) { + List palindromicPrimes = new ArrayList<>(); + if (n <= 0) { + return palindromicPrimes; // Handle case for 0 or negative input } - System.out.print(2 + "\n"); // print the first Palindromic Prime + + palindromicPrimes.add(2); // 2 is the first palindromic prime int count = 1; int num = 3; - while (count < y) { - if (num == reverse(num) && prime(num)) { // number is prime and it's reverse is same - count++; // counts check when to terminate while loop - System.out.print(num + "\n"); // print the Palindromic Prime + + while (count < n) { + if (num == reverse(num) && prime(num)) { + palindromicPrimes.add(num); + count++; } - num += 2; // inrease iterator value by two + num += 2; // Skip even numbers } + return palindromicPrimes; } } diff --git a/src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java b/src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java new file mode 100644 index 00000000000..130cd19b47b --- /dev/null +++ b/src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java @@ -0,0 +1,53 @@ +package com.thealgorithms.misc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.Test; + +public class PalindromePrimeTest { + + @Test + public void testPrimeWithPrimeNumbers() { + assertTrue(PalindromePrime.prime(2), "2 should be prime"); + assertTrue(PalindromePrime.prime(3), "3 should be prime"); + assertTrue(PalindromePrime.prime(5), "5 should be prime"); + assertTrue(PalindromePrime.prime(11), "11 should be prime"); + } + + @Test + public void testPrimeWithNonPrimeNumbers() { + assertFalse(PalindromePrime.prime(1), "1 is not prime"); + assertFalse(PalindromePrime.prime(4), "4 is not prime"); + assertFalse(PalindromePrime.prime(9), "9 is not prime"); + assertFalse(PalindromePrime.prime(15), "15 is not prime"); + } + + @Test + public void testReverse() { + assertEquals(123, PalindromePrime.reverse(321), "Reverse of 321 should be 123"); + assertEquals(7, PalindromePrime.reverse(7), "Reverse of 7 should be 7"); + assertEquals(1221, PalindromePrime.reverse(1221), "Reverse of 1221 should be 1221"); + } + + @Test + public void testGeneratePalindromePrimes() { + List result = PalindromePrime.generatePalindromePrimes(5); + List expected = List.of(2, 3, 5, 7, 11); + assertEquals(expected, result, "The first 5 palindromic primes should be [2, 3, 5, 7, 11]"); + } + + @Test + public void testGeneratePalindromePrimesWithZero() { + List result = PalindromePrime.generatePalindromePrimes(0); + assertTrue(result.isEmpty(), "Generating 0 palindromic primes should return an empty list"); + } + + @Test + public void testGeneratePalindromePrimesWithNegativeInput() { + List result = PalindromePrime.generatePalindromePrimes(-5); + assertTrue(result.isEmpty(), "Generating a negative number of palindromic primes should return an empty list"); + } +} From f1aceea732b1766c0f989f7b6f08ea4fd1ba3ee0 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:07:35 +0530 Subject: [PATCH 15/75] Enhance class & function documentation in `CircularBuffer.java` (#5582) --- .../buffers/CircularBuffer.java | 82 ++++++++- .../buffers/CircularBufferTest.java | 163 ++++++------------ 2 files changed, 129 insertions(+), 116 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java b/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java index 15e9a095622..b709e16fd1f 100644 --- a/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java +++ b/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java @@ -2,27 +2,62 @@ import java.util.concurrent.atomic.AtomicInteger; +/** + * The {@code CircularBuffer} class implements a generic circular (or ring) buffer. + * A circular buffer is a fixed-size data structure that operates in a FIFO (First In, First Out) manner. + * The buffer allows you to overwrite old data when the buffer is full and efficiently use limited memory. + * When the buffer is full, adding a new item will overwrite the oldest data. + * + * @param The type of elements stored in the circular buffer. + */ public class CircularBuffer { private final Item[] buffer; private final CircularPointer putPointer; private final CircularPointer getPointer; private final AtomicInteger size = new AtomicInteger(0); + /** + * Constructor to initialize the circular buffer with a specified size. + * + * @param size The size of the circular buffer. + * @throws IllegalArgumentException if the size is zero or negative. + */ public CircularBuffer(int size) { + if (size <= 0) { + throw new IllegalArgumentException("Buffer size must be positive"); + } // noinspection unchecked this.buffer = (Item[]) new Object[size]; this.putPointer = new CircularPointer(0, size); this.getPointer = new CircularPointer(0, size); } + /** + * Checks if the circular buffer is empty. + * This method is based on the current size of the buffer. + * + * @return {@code true} if the buffer is empty, {@code false} otherwise. + */ public boolean isEmpty() { return size.get() == 0; } + /** + * Checks if the circular buffer is full. + * The buffer is considered full when its size equals its capacity. + * + * @return {@code true} if the buffer is full, {@code false} otherwise. + */ public boolean isFull() { return size.get() == buffer.length; } + /** + * Retrieves and removes the item at the front of the buffer (FIFO). + * This operation will move the {@code getPointer} forward. + * + * @return The item at the front of the buffer, or {@code null} if the buffer is empty. + */ public Item get() { if (isEmpty()) { return null; @@ -33,31 +68,64 @@ public Item get() { return item; } + /** + * Adds an item to the end of the buffer (FIFO). + * If the buffer is full, this operation will overwrite the oldest data. + * + * @param item The item to be added. + * @throws IllegalArgumentException if the item is null. + * @return {@code true} if the item was successfully added, {@code false} if the buffer was full and the item overwrote existing data. + */ public boolean put(Item item) { + if (item == null) { + throw new IllegalArgumentException("Null items are not allowed"); + } + + boolean wasEmpty = isEmpty(); if (isFull()) { - return false; + getPointer.getAndIncrement(); // Move get pointer to discard oldest item + } else { + size.incrementAndGet(); } buffer[putPointer.getAndIncrement()] = item; - size.incrementAndGet(); - return true; + return wasEmpty; } + /** + * The {@code CircularPointer} class is a helper class used to track the current index (pointer) + * in the circular buffer. + * The max value represents the capacity of the buffer. + * The `CircularPointer` class ensures that the pointer automatically wraps around to 0 + * when it reaches the maximum index. + * This is achieved in the `getAndIncrement` method, where the pointer + * is incremented and then taken modulo the maximum value (`max`). + * This operation ensures that the pointer always stays within the bounds of the buffer. + */ private static class CircularPointer { private int pointer; private final int max; + /** + * Constructor to initialize the circular pointer. + * + * @param pointer The initial position of the pointer. + * @param max The maximum size (capacity) of the circular buffer. + */ CircularPointer(int pointer, int max) { this.pointer = pointer; this.max = max; } + /** + * Increments the pointer by 1 and wraps it around to 0 if it reaches the maximum value. + * This ensures the pointer always stays within the buffer's bounds. + * + * @return The current pointer value before incrementing. + */ public int getAndIncrement() { - if (pointer == max) { - pointer = 0; - } int tmp = pointer; - pointer++; + pointer = (pointer + 1) % max; return tmp; } } diff --git a/src/test/java/com/thealgorithms/datastructures/buffers/CircularBufferTest.java b/src/test/java/com/thealgorithms/datastructures/buffers/CircularBufferTest.java index be98fde484f..b115fc187b1 100644 --- a/src/test/java/com/thealgorithms/datastructures/buffers/CircularBufferTest.java +++ b/src/test/java/com/thealgorithms/datastructures/buffers/CircularBufferTest.java @@ -1,143 +1,88 @@ package com.thealgorithms.datastructures.buffers; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicIntegerArray; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; class CircularBufferTest { - private static final int BUFFER_SIZE = 10; - private CircularBuffer buffer; - - @BeforeEach - void setUp() { - buffer = new CircularBuffer<>(BUFFER_SIZE); - } @Test - void isEmpty() { + void testInitialization() { + CircularBuffer buffer = new CircularBuffer<>(5); assertTrue(buffer.isEmpty()); - buffer.put(generateInt()); - assertFalse(buffer.isEmpty()); + assertEquals(Boolean.FALSE, buffer.isFull()); } @Test - void isFull() { - assertFalse(buffer.isFull()); - buffer.put(generateInt()); - assertFalse(buffer.isFull()); + void testPutAndGet() { + CircularBuffer buffer = new CircularBuffer<>(3); - for (int i = 1; i < BUFFER_SIZE; i++) { - buffer.put(generateInt()); - } + assertTrue(buffer.put("A")); + assertEquals(Boolean.FALSE, buffer.isEmpty()); + assertEquals(Boolean.FALSE, buffer.isFull()); + + buffer.put("B"); + buffer.put("C"); assertTrue(buffer.isFull()); + + assertEquals("A", buffer.get()); + assertEquals("B", buffer.get()); + assertEquals("C", buffer.get()); + assertTrue(buffer.isEmpty()); } @Test - void get() { - assertNull(buffer.get()); - for (int i = 0; i < 100; i++) { - buffer.put(i); - } - for (int i = 0; i < BUFFER_SIZE; i++) { - assertEquals(i, buffer.get()); - } + void testOverwrite() { + CircularBuffer buffer = new CircularBuffer<>(3); + + buffer.put(1); + buffer.put(2); + buffer.put(3); + assertEquals(Boolean.FALSE, buffer.put(4)); // This should overwrite 1 + + assertEquals(2, buffer.get()); + assertEquals(3, buffer.get()); + assertEquals(4, buffer.get()); assertNull(buffer.get()); } @Test - void put() { - for (int i = 0; i < BUFFER_SIZE; i++) { - assertTrue(buffer.put(generateInt())); - } - assertFalse(buffer.put(generateInt())); + void testEmptyBuffer() { + CircularBuffer buffer = new CircularBuffer<>(2); + assertNull(buffer.get()); } - @RepeatedTest(1000) - void concurrentTest() throws InterruptedException { - final int numberOfThreadsForProducers = 3; - final int numberOfThreadsForConsumers = 2; - final int numberOfItems = 300; - final CountDownLatch producerCountDownLatch = new CountDownLatch(numberOfItems); - final CountDownLatch consumerCountDownLatch = new CountDownLatch(numberOfItems); - final AtomicIntegerArray resultAtomicArray = new AtomicIntegerArray(numberOfItems); - - // We are running 2 ExecutorService simultaneously 1 - producer, 2 - consumer - // Run producer threads to populate buffer. - ExecutorService putExecutors = Executors.newFixedThreadPool(numberOfThreadsForProducers); - putExecutors.execute(() -> { - while (producerCountDownLatch.getCount() > 0) { - int count = (int) producerCountDownLatch.getCount(); - boolean put = buffer.put(count); - while (!put) { - put = buffer.put(count); - } - producerCountDownLatch.countDown(); - } - }); - - // Run consumer threads to retrieve the data from buffer. - ExecutorService getExecutors = Executors.newFixedThreadPool(numberOfThreadsForConsumers); - getExecutors.execute(() -> { - while (consumerCountDownLatch.getCount() > 0) { - int count = (int) consumerCountDownLatch.getCount(); - Integer item = buffer.get(); - while (item == null) { - item = buffer.get(); - } - resultAtomicArray.set(count - 1, item); - consumerCountDownLatch.countDown(); - } - }); - - producerCountDownLatch.await(); - consumerCountDownLatch.await(); - putExecutors.shutdown(); - getExecutors.shutdown(); - shutDownExecutorSafely(putExecutors); - shutDownExecutorSafely(getExecutors); - - List resultArray = getSortedListFrom(resultAtomicArray); - for (int i = 0; i < numberOfItems; i++) { - int expectedItem = i + 1; - assertEquals(expectedItem, resultArray.get(i)); - } + @Test + void testFullBuffer() { + CircularBuffer buffer = new CircularBuffer<>(2); + buffer.put('A'); + buffer.put('B'); + assertTrue(buffer.isFull()); + assertEquals(Boolean.FALSE, buffer.put('C')); // This should overwrite 'A' + assertEquals('B', buffer.get()); + assertEquals('C', buffer.get()); } - private int generateInt() { - return ThreadLocalRandom.current().nextInt(0, 100); - } + @Test + void testIllegalArguments() { + assertThrows(IllegalArgumentException.class, () -> new CircularBuffer<>(0)); + assertThrows(IllegalArgumentException.class, () -> new CircularBuffer<>(-1)); - private void shutDownExecutorSafely(ExecutorService executorService) { - try { - if (!executorService.awaitTermination(1_000, TimeUnit.MILLISECONDS)) { - executorService.shutdownNow(); - } - } catch (InterruptedException e) { - executorService.shutdownNow(); - } + CircularBuffer buffer = new CircularBuffer<>(1); + assertThrows(IllegalArgumentException.class, () -> buffer.put(null)); } - public List getSortedListFrom(AtomicIntegerArray atomicArray) { - int length = atomicArray.length(); - ArrayList result = new ArrayList<>(length); - for (int i = 0; i < length; i++) { - result.add(atomicArray.get(i)); + @Test + void testLargeBuffer() { + CircularBuffer buffer = new CircularBuffer<>(1000); + for (int i = 0; i < 1000; i++) { + buffer.put(i); } - result.sort(Comparator.comparingInt(o -> o)); - return result; + assertTrue(buffer.isFull()); + buffer.put(1000); // This should overwrite 0 + assertEquals(1, buffer.get()); } } From 9eff71bf0512253dc3db02356f51bb48fcc25aab Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:11:26 +0530 Subject: [PATCH 16/75] Add tests for `ConvolutionFFT` (#5767) --- DIRECTORY.md | 1 + .../java/com/thealgorithms/maths/FFT.java | 8 +++ .../maths/ConvolutionFFTTest.java | 55 +++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 src/test/java/com/thealgorithms/maths/ConvolutionFFTTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index d0de70715bb..8d66f77d1ea 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -938,6 +938,7 @@ * [CeilTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/CeilTest.java) * [CollatzConjectureTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/CollatzConjectureTest.java) * [CombinationsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/CombinationsTest.java) + * [ConvolutionFFTTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/ConvolutionFFTTest.java) * [ConvolutionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/ConvolutionTest.java) * [CrossCorrelationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/CrossCorrelationTest.java) * [DeterminantOfMatrixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/DeterminantOfMatrixTest.java) diff --git a/src/main/java/com/thealgorithms/maths/FFT.java b/src/main/java/com/thealgorithms/maths/FFT.java index 47605f010b2..91754bd1a80 100644 --- a/src/main/java/com/thealgorithms/maths/FFT.java +++ b/src/main/java/com/thealgorithms/maths/FFT.java @@ -165,6 +165,14 @@ public Complex divide(double n) { temp.img = this.img / n; return temp; } + + public double real() { + return real; + } + + public double imaginary() { + return img; + } } /** diff --git a/src/test/java/com/thealgorithms/maths/ConvolutionFFTTest.java b/src/test/java/com/thealgorithms/maths/ConvolutionFFTTest.java new file mode 100644 index 00000000000..4d627f939d4 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/ConvolutionFFTTest.java @@ -0,0 +1,55 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class ConvolutionFFTTest { + + /** + * Helper method to create a complex signal from an array of doubles. + */ + private ArrayList createComplexSignal(double[] values) { + ArrayList signal = new ArrayList<>(); + for (double value : values) { + signal.add(new FFT.Complex(value, 0)); + } + return signal; + } + + /** + * Helper method to compare two complex signals for equality within a small margin of error. + */ + private void assertComplexArrayEquals(List expected, List result, double delta) { + assertEquals(expected.size(), result.size(), "Signal lengths are not equal."); + for (int i = 0; i < expected.size(); i++) { + FFT.Complex expectedValue = expected.get(i); + FFT.Complex resultValue = result.get(i); + assertEquals(expectedValue.real(), resultValue.real(), delta, "Real part mismatch at index " + i); + assertEquals(expectedValue.imaginary(), resultValue.imaginary(), delta, "Imaginary part mismatch at index " + i); + } + } + + @ParameterizedTest(name = "Test case {index}: {3}") + @MethodSource("provideTestCases") + public void testConvolutionFFT(double[] a, double[] b, double[] expectedOutput, String testDescription) { + ArrayList signalA = createComplexSignal(a); + ArrayList signalB = createComplexSignal(b); + + ArrayList expected = createComplexSignal(expectedOutput); + ArrayList result = ConvolutionFFT.convolutionFFT(signalA, signalB); + + assertComplexArrayEquals(expected, result, 1e-9); // Allow small margin of error + } + + private static Stream provideTestCases() { + return Stream.of(Arguments.of(new double[] {1, 2, 3}, new double[] {4, 5, 6}, new double[] {4, 13, 28, 27, 18}, "Basic test"), Arguments.of(new double[] {0, 0, 0}, new double[] {1, 2, 3}, new double[] {0, 0, 0, 0, 0}, "Test with zero elements"), + Arguments.of(new double[] {1, 2}, new double[] {3, 4, 5}, new double[] {3, 10, 13, 10}, "Test with different sizes"), Arguments.of(new double[] {5}, new double[] {2}, new double[] {10}, "Test with single element"), + Arguments.of(new double[] {1, -2, 3}, new double[] {-1, 2, -3}, new double[] {-1, 4, -10, 12, -9}, "Test with negative values")); + } +} From 7e11e9bb82dbf898447a53bbea5ebe39632d20a2 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:31:06 +0530 Subject: [PATCH 17/75] Add tests for `EulerMethod` (#5769) --- DIRECTORY.md | 1 + .../thealgorithms/maths/EulerMethodTest.java | 79 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 src/test/java/com/thealgorithms/maths/EulerMethodTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 8d66f77d1ea..398f61ec844 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -945,6 +945,7 @@ * [DigitalRootTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/DigitalRootTest.java) * [DistanceFormulaTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/DistanceFormulaTest.java) * [DudeneyNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/DudeneyNumberTest.java) + * [EulerMethodTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/EulerMethodTest.java) * [EulersFunctionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/EulersFunctionTest.java) * [FactorialRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FactorialRecursionTest.java) * [FactorialTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FactorialTest.java) diff --git a/src/test/java/com/thealgorithms/maths/EulerMethodTest.java b/src/test/java/com/thealgorithms/maths/EulerMethodTest.java new file mode 100644 index 00000000000..5ae5ac70b1d --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/EulerMethodTest.java @@ -0,0 +1,79 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.function.BiFunction; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class EulerMethodTest { + + private static class EulerFullTestCase { + double[] params; + BiFunction equation; + int expectedSize; + double[] expectedFirstPoint; + double[] expectedLastPoint; + + EulerFullTestCase(double[] params, BiFunction equation, int expectedSize, double[] expectedFirstPoint, double[] expectedLastPoint) { + this.params = params; + this.equation = equation; + this.expectedSize = expectedSize; + this.expectedFirstPoint = expectedFirstPoint; + this.expectedLastPoint = expectedLastPoint; + } + } + + @ParameterizedTest + @MethodSource("eulerStepTestCases") + void testEulerStep(double x, double h, double y, BiFunction equation, double expected) { + double result = EulerMethod.eulerStep(x, h, y, equation); + assertEquals(expected, result, 1e-9, "Euler step failed for the given equation."); + } + + static Stream eulerStepTestCases() { + return Stream.of(Arguments.of(0.0, 0.1, 1.0, (BiFunction) ((x, y) -> x + y), 1.1)); + } + + @ParameterizedTest + @MethodSource("eulerStepInvalidCases") + void testEulerStepInvalidInput(double x, double h, double y, BiFunction equation, Class expectedExceptionClass) { + assertThrows(expectedExceptionClass, () -> EulerMethod.eulerStep(x, h, y, equation)); + } + + static Stream eulerStepInvalidCases() { + BiFunction dummyEquation = (x, y) -> x + y; + return Stream.of(Arguments.of(0.0, -0.1, 1.0, dummyEquation, IllegalArgumentException.class), Arguments.of(0.0, 0.0, 1.0, dummyEquation, IllegalArgumentException.class)); + } + + @ParameterizedTest + @MethodSource("eulerFullTestCases") + void testEulerFull(EulerFullTestCase testCase) { + ArrayList result = EulerMethod.eulerFull(testCase.params[0], testCase.params[1], testCase.params[2], testCase.params[3], testCase.equation); + assertEquals(testCase.expectedSize, result.size(), "Incorrect number of points in the result."); + assertArrayEquals(testCase.expectedFirstPoint, result.get(0), 1e-9, "Incorrect first point."); + assertArrayEquals(testCase.expectedLastPoint, result.get(result.size() - 1), 1e-9, "Incorrect last point."); + } + + static Stream eulerFullTestCases() { + return Stream.of(Arguments.of(new EulerFullTestCase(new double[] {0.0, 1.0, 0.5, 0.0}, (x, y) -> x, 3, new double[] {0.0, 0.0}, new double[] {1.0, 0.25})), + Arguments.of(new EulerFullTestCase(new double[] {0.0, 1.0, 0.1, 1.0}, (x, y) -> y, 12, new double[] {0.0, 1.0}, new double[] {1.0999999999999999, 2.8531167061100002})), + Arguments.of(new EulerFullTestCase(new double[] {0.0, 0.1, 0.1, 1.0}, (x, y) -> x + y, 2, new double[] {0.0, 1.0}, new double[] {0.1, 1.1}))); + } + + @ParameterizedTest + @MethodSource("eulerFullInvalidCases") + void testEulerFullInvalidInput(double xStart, double xEnd, double stepSize, double yInitial, BiFunction equation, Class expectedExceptionClass) { + assertThrows(expectedExceptionClass, () -> EulerMethod.eulerFull(xStart, xEnd, stepSize, yInitial, equation)); + } + + static Stream eulerFullInvalidCases() { + BiFunction dummyEquation = (x, y) -> x + y; + return Stream.of(Arguments.of(1.0, 0.0, 0.1, 1.0, dummyEquation, IllegalArgumentException.class), Arguments.of(0.0, 1.0, 0.0, 1.0, dummyEquation, IllegalArgumentException.class), Arguments.of(0.0, 1.0, -0.1, 1.0, dummyEquation, IllegalArgumentException.class)); + } +} From 3e10f8f1d8cbcc7f675ae1b6395b1e2e8b2800a4 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Tue, 15 Oct 2024 20:22:48 +0530 Subject: [PATCH 18/75] Add tests, remove `main`, fix bug in `Sparsity` (#5780) --- DIRECTORY.md | 1 + .../java/com/thealgorithms/misc/Sparsity.java | 28 ++++----------- .../com/thealgorithms/misc/SparsityTest.java | 36 +++++++++++++++++++ 3 files changed, 43 insertions(+), 22 deletions(-) create mode 100644 src/test/java/com/thealgorithms/misc/SparsityTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 398f61ec844..652ecbfd678 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1033,6 +1033,7 @@ * [PalindromePrimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java) * [PalindromeSinglyLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java) * [RangeInSortedArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java) + * [SparsityTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/SparsityTest.java) * [ThreeSumProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/ThreeSumProblemTest.java) * [TwoSumProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/TwoSumProblemTest.java) * [WordBoggleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/WordBoggleTest.java) diff --git a/src/main/java/com/thealgorithms/misc/Sparsity.java b/src/main/java/com/thealgorithms/misc/Sparsity.java index cae2fbdead9..08e50a121da 100644 --- a/src/main/java/com/thealgorithms/misc/Sparsity.java +++ b/src/main/java/com/thealgorithms/misc/Sparsity.java @@ -1,7 +1,5 @@ package com.thealgorithms.misc; -import java.util.Scanner; - /* *A matrix is sparse if many of its coefficients are zero (In general if 2/3rd of matrix elements *are 0, it is considered as sparse). The interest in sparsity arises because its exploitation can @@ -16,12 +14,17 @@ private Sparsity() { } /* + * @param mat the input matrix * @return Sparsity of matrix * * where sparsity = number of zeroes/total elements in matrix * */ static double sparsity(double[][] mat) { + if (mat == null || mat.length == 0) { + throw new IllegalArgumentException("Matrix cannot be null or empty"); + } + int zero = 0; // Traversing the matrix to count number of zeroes for (int i = 0; i < mat.length; i++) { @@ -32,25 +35,6 @@ static double sparsity(double[][] mat) { } } // return sparsity - return ((double) zero / (mat.length * mat[1].length)); - } - - // Driver method - public static void main(String[] args) { - Scanner in = new Scanner(System.in); - System.out.println("Enter number of rows in matrix: "); - int n = in.nextInt(); - System.out.println("Enter number of Columns in matrix: "); - int m = in.nextInt(); - - System.out.println("Enter Matrix elements: "); - double[][] mat = new double[n][m]; - for (int i = 0; i < n; i++) { - for (int j = 0; j < m; j++) { - mat[i][j] = in.nextDouble(); - } - } - System.out.println("Sparsity of matrix is: " + sparsity(mat)); - in.close(); + return ((double) zero / (mat.length * mat[0].length)); } } diff --git a/src/test/java/com/thealgorithms/misc/SparsityTest.java b/src/test/java/com/thealgorithms/misc/SparsityTest.java new file mode 100644 index 00000000000..b93e4f44937 --- /dev/null +++ b/src/test/java/com/thealgorithms/misc/SparsityTest.java @@ -0,0 +1,36 @@ +package com.thealgorithms.misc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class SparsityTest { + + private static final double DELTA = 1e-9; + + @ParameterizedTest(name = "Test case {index}: {2}") + @MethodSource("provideTestCases") + public void testSparsity(double[][] matrix, double expectedSparsity, String description) { + assertEquals(expectedSparsity, Sparsity.sparsity(matrix), DELTA, description); + } + + private static Stream provideTestCases() { + return Stream.of(Arguments.of(new double[][] {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, 1.0, "Matrix with all zero elements"), Arguments.of(new double[][] {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}, 0.0, "Matrix with no zero elements"), + Arguments.of(new double[][] {{0, 2, 0}, {4, 0, 6}, {0, 8, 0}}, 5.0 / 9.0, "Matrix with mixed elements"), Arguments.of(new double[][] {{0, 1, 0, 2, 0}}, 3.0 / 5.0, "Single-row matrix"), Arguments.of(new double[][] {{1}, {0}, {0}, {2}}, 2.0 / 4.0, "Single-column matrix"), + Arguments.of(new double[][] {{0}}, 1.0, "Matrix with a single zero element"), Arguments.of(new double[][] {{5}}, 0.0, "Matrix with a single non-zero element")); + } + + @ParameterizedTest(name = "Test case {index}: {1}") + @MethodSource("provideExceptionTestCases") + public void testSparsityExceptions(double[][] matrix, String description) { + assertThrows(IllegalArgumentException.class, () -> Sparsity.sparsity(matrix), description); + } + + private static Stream provideExceptionTestCases() { + return Stream.of(Arguments.of(new double[][] {}, "Empty matrix should throw IllegalArgumentException")); + } +} From b35f98a67aff39afc939ea07b4ad339b5a4a77a6 Mon Sep 17 00:00:00 2001 From: Varnan Rathod <119997446+Krounosity@users.noreply.github.com> Date: Tue, 15 Oct 2024 20:53:10 +0530 Subject: [PATCH 19/75] Add Rail Fence Cipher (#5761) --- DIRECTORY.md | 2 + .../ciphers/RailFenceCipher.java | 147 ++++++++++++++++++ .../thealgorithms/ciphers/RailFenceTest.java | 62 ++++++++ 3 files changed, 211 insertions(+) create mode 100644 src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java create mode 100644 src/test/java/com/thealgorithms/ciphers/RailFenceTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 652ecbfd678..3d215855b85 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -73,6 +73,7 @@ * [PlayfairCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/PlayfairCipher.java) * [Polybius](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Polybius.java) * [ProductCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/ProductCipher.java) + * [RailFenceCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java) * [RSA](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/RSA.java) * [SimpleSubCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/SimpleSubCipher.java) * [Vigenere](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Vigenere.java) @@ -729,6 +730,7 @@ * [HillCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/HillCipherTest.java) * [PlayfairTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/PlayfairTest.java) * [PolybiusTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/PolybiusTest.java) + * [RailFenceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/RailFenceTest.java) * [RSATest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/RSATest.java) * [SimpleSubCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/SimpleSubCipherTest.java) * [VigenereTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/VigenereTest.java) diff --git a/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java b/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java new file mode 100644 index 00000000000..f8125298046 --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java @@ -0,0 +1,147 @@ +package com.thealgorithms.ciphers; + +import java.util.Arrays; + +/** + * The rail fence cipher (also called a zigzag cipher) is a classical type of transposition cipher. + * It derives its name from the manner in which encryption is performed, in analogy to a fence built with horizontal rails. + * https://en.wikipedia.org/wiki/Rail_fence_cipher + * @author https://github.com/Krounosity + */ + +public class RailFenceCipher { + + // Encrypts the input string using the rail fence cipher method with the given number of rails. + public String encrypt(String str, int rails) { + + // Base case of single rail or rails are more than the number of characters in the string + if (rails == 1 || rails >= str.length()) { + return str; + } + + // Boolean flag to determine if the movement is downward or upward in the rail matrix. + boolean down = true; + // Create a 2D array to represent the rails (rows) and the length of the string (columns). + char[][] strRail = new char[rails][str.length()]; + + // Initialize all positions in the rail matrix with a placeholder character ('\n'). + for (int i = 0; i < rails; i++) { + Arrays.fill(strRail[i], '\n'); + } + + int row = 0; // Start at the first row + int col = 0; // Start at the first column + + int i = 0; + + // Fill the rail matrix with characters from the string based on the rail pattern. + while (col < str.length()) { + // Change direction to down when at the first row. + if (row == 0) { + down = true; + } + // Change direction to up when at the last row. + else if (row == rails - 1) { + down = false; + } + + // Place the character in the current position of the rail matrix. + strRail[row][col] = str.charAt(i); + col++; // Move to the next column. + // Move to the next row based on the direction. + if (down) { + row++; + } else { + row--; + } + + i++; + } + + // Construct the encrypted string by reading characters row by row. + StringBuilder encryptedString = new StringBuilder(); + for (char[] chRow : strRail) { + for (char ch : chRow) { + if (ch != '\n') { + encryptedString.append(ch); + } + } + } + return encryptedString.toString(); + } + // Decrypts the input string using the rail fence cipher method with the given number of rails. + public String decrypt(String str, int rails) { + + // Base case of single rail or rails are more than the number of characters in the string + if (rails == 1 || rails >= str.length()) { + return str; + } + // Boolean flag to determine if the movement is downward or upward in the rail matrix. + boolean down = true; + + // Create a 2D array to represent the rails (rows) and the length of the string (columns). + char[][] strRail = new char[rails][str.length()]; + + int row = 0; // Start at the first row + int col = 0; // Start at the first column + + // Mark the pattern on the rail matrix using '*'. + while (col < str.length()) { + // Change direction to down when at the first row. + if (row == 0) { + down = true; + } + // Change direction to up when at the last row. + else if (row == rails - 1) { + down = false; + } + + // Mark the current position in the rail matrix. + strRail[row][col] = '*'; + col++; // Move to the next column. + // Move to the next row based on the direction. + if (down) { + row++; + } else { + row--; + } + } + + int index = 0; // Index to track characters from the input string. + // Fill the rail matrix with characters from the input string based on the marked pattern. + for (int i = 0; i < rails; i++) { + for (int j = 0; j < str.length(); j++) { + if (strRail[i][j] == '*') { + strRail[i][j] = str.charAt(index++); + } + } + } + + // Construct the decrypted string by following the zigzag pattern. + StringBuilder decryptedString = new StringBuilder(); + row = 0; // Reset to the first row + col = 0; // Reset to the first column + + while (col < str.length()) { + // Change direction to down when at the first row. + if (row == 0) { + down = true; + } + // Change direction to up when at the last row. + else if (row == rails - 1) { + down = false; + } + // Append the character from the rail matrix to the decrypted string. + decryptedString.append(strRail[row][col]); + col++; // Move to the next column. + // Move to the next row based on the direction. + if (down) { + row++; + } else { + row--; + } + } + + return decryptedString.toString(); + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/RailFenceTest.java b/src/test/java/com/thealgorithms/ciphers/RailFenceTest.java new file mode 100644 index 00000000000..2bfa704e3d0 --- /dev/null +++ b/src/test/java/com/thealgorithms/ciphers/RailFenceTest.java @@ -0,0 +1,62 @@ +package com.thealgorithms.ciphers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class RailFenceTest { + + @Test + void testEncryption() { + RailFenceCipher cipher = new RailFenceCipher(); + + String input = "We are discovered! Flee at once"; + int rails = 3; + String encrypted = cipher.encrypt(input, rails); + assertEquals("Wrivdlaneaedsoee!Fe toc cr e e", encrypted); + + String singleChar = "A"; + int singleRail = 2; + String encryptedSingleChar = cipher.encrypt(singleChar, singleRail); + assertEquals("A", encryptedSingleChar); + + String shortString = "Hello"; + int moreRails = 10; + String encryptedShortString = cipher.encrypt(shortString, moreRails); + assertEquals("Hello", encryptedShortString); + + String inputSingleRail = "Single line"; + int singleRailOnly = 1; + String encryptedSingleRail = cipher.encrypt(inputSingleRail, singleRailOnly); + assertEquals("Single line", encryptedSingleRail); + } + + @Test + void testDecryption() { + RailFenceCipher cipher = new RailFenceCipher(); + + // Scenario 1: Basic decryption with multiple rails + String encryptedInput = "Wrivdlaneaedsoee!Fe toc cr e e"; + int rails = 3; + String decrypted = cipher.decrypt(encryptedInput, rails); + assertEquals("We are discovered! Flee at once", decrypted); + + // Scenario 2: Single character string decryption + String encryptedSingleChar = "A"; + int singleRail = 2; // More than 1 rail + String decryptedSingleChar = cipher.decrypt(encryptedSingleChar, singleRail); + assertEquals("A", decryptedSingleChar); + + // Scenario 3: String length less than the number of rails + String encryptedShortString = "Hello"; + int moreRails = 10; // More rails than characters + String decryptedShortString = cipher.decrypt(encryptedShortString, moreRails); + assertEquals("Hello", decryptedShortString); + + // Scenario 4: Single rail decryption (output should be the same as input) + String encryptedSingleRail = "Single line"; + int singleRailOnly = 1; + String decryptedSingleRail = cipher.decrypt(encryptedSingleRail, singleRailOnly); + assertEquals("Single line", decryptedSingleRail); + } +} From 2a518e3c9a44c4333c5ec9283895132264773afb Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Tue, 15 Oct 2024 21:25:27 +0530 Subject: [PATCH 20/75] Add `Abbreviation` algorithm (#5790) --- DIRECTORY.md | 2 + .../dynamicprogramming/Abbreviation.java | 56 +++++++++++++++++++ .../dynamicprogramming/AbbreviationTest.java | 44 +++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 src/main/java/com/thealgorithms/dynamicprogramming/Abbreviation.java create mode 100644 src/test/java/com/thealgorithms/dynamicprogramming/AbbreviationTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 3d215855b85..6429757179e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -261,6 +261,7 @@ * [StrassenMatrixMultiplication](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/StrassenMatrixMultiplication.java) * [TilingProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/TilingProblem.java) * dynamicprogramming + * [Abbreviation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/Abbreviation.java) * [BoardPath](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/BoardPath.java) * [BoundaryFill](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/BoundaryFill.java) * [BruteForceKnapsack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsack.java) @@ -862,6 +863,7 @@ * [StrassenMatrixMultiplicationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/StrassenMatrixMultiplicationTest.java) * [TilingProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/TilingProblemTest.java) * dynamicprogramming + * [AbbreviationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/AbbreviationTest.java) * [BoardPathTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/BoardPathTest.java) * [BoundaryFillTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/BoundaryFillTest.java) * [BruteForceKnapsackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsackTest.java) diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/Abbreviation.java b/src/main/java/com/thealgorithms/dynamicprogramming/Abbreviation.java new file mode 100644 index 00000000000..60c0fd0a3cd --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/Abbreviation.java @@ -0,0 +1,56 @@ +package com.thealgorithms.dynamicprogramming; + +/** + * A class that provides a solution to the abbreviation problem. + * + * Problem: Given two strings, `a` and `b`, determine if string `a` can be + * transformed into string `b` by performing the following operations: + * 1. Capitalize zero or more of `a`'s lowercase letters (i.e., convert them to uppercase). + * 2. Delete any of the remaining lowercase letters from `a`. + * + * The task is to determine whether it is possible to make string `a` equal to string `b`. + * + * @author Hardvan + */ +public final class Abbreviation { + private Abbreviation() { + } + + /** + * Determines if string `a` can be transformed into string `b` by capitalizing + * some of its lowercase letters and deleting the rest. + * + * @param a The input string which may contain both uppercase and lowercase letters. + * @param b The target string containing only uppercase letters. + * @return {@code true} if string `a` can be transformed into string `b`, + * {@code false} otherwise. + * + * Time Complexity: O(n * m) where n = length of string `a` and m = length of string `b`. + * Space Complexity: O(n * m) due to the dynamic programming table. + */ + public static boolean abbr(String a, String b) { + int n = a.length(); + int m = b.length(); + + boolean[][] dp = new boolean[n + 1][m + 1]; + + dp[0][0] = true; + + for (int i = 0; i < n; i++) { + for (int j = 0; j <= m; j++) { + if (dp[i][j]) { + // Case 1: If the current characters match (or can be capitalized to match) + if (j < m && Character.toUpperCase(a.charAt(i)) == b.charAt(j)) { + dp[i + 1][j + 1] = true; + } + // Case 2: If the character in `a` is lowercase, we can skip it + if (Character.isLowerCase(a.charAt(i))) { + dp[i + 1][j] = true; + } + } + } + } + + return dp[n][m]; + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/AbbreviationTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/AbbreviationTest.java new file mode 100644 index 00000000000..4e36edbd777 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/AbbreviationTest.java @@ -0,0 +1,44 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class AbbreviationTest { + + @ParameterizedTest + @MethodSource("provideTestCases") + public void testAbbreviation(String a, String b, boolean expected) { + assertEquals(expected, Abbreviation.abbr(a, b)); + } + + private static Stream provideTestCases() { + return Stream.of( + // Example test case from problem description + Arguments.of("daBcd", "ABC", Boolean.TRUE), + + // Test case where transformation is impossible + Arguments.of("dBcd", "ABC", Boolean.FALSE), + + // Test case with exact match (all uppercase) + Arguments.of("ABC", "ABC", Boolean.TRUE), + + // Test case where input string contains all required letters plus extra lowercase letters + Arguments.of("aAbBcC", "ABC", Boolean.TRUE), + + // Test case with only lowercase letters in input + Arguments.of("abcd", "ABCD", Boolean.TRUE), + + // Test case with an empty second string (b) + Arguments.of("abc", "", Boolean.TRUE), + + // Test case with an empty first string (a) but non-empty second string (b) + Arguments.of("", "A", Boolean.FALSE), + + // Complex case with interleaved letters + Arguments.of("daBcAbCd", "ABCD", Boolean.FALSE)); + } +} From a53765404923eeba40867cf32fc34709e9ab2d4a Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 16 Oct 2024 14:00:27 +0530 Subject: [PATCH 21/75] Add `StockProfitCalculator` (#5793) --- DIRECTORY.md | 2 ++ .../StockProfitCalculator.java | 34 +++++++++++++++++++ .../StockProfitCalculatorTest.java | 21 ++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 src/main/java/com/thealgorithms/greedyalgorithms/StockProfitCalculator.java create mode 100644 src/test/java/com/thealgorithms/greedyalgorithms/StockProfitCalculatorTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 6429757179e..aa34bf1a953 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -320,6 +320,7 @@ * [JobSequencing](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/JobSequencing.java) * [MergeIntervals](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/MergeIntervals.java) * [MinimizingLateness](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/MinimizingLateness.java) + * [StockProfitCalculator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/StockProfitCalculator.java) * io * [BufferedReader](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/io/BufferedReader.java) * lineclipping @@ -920,6 +921,7 @@ * [JobSequencingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/JobSequencingTest.java) * [MergeIntervalsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/MergeIntervalsTest.java) * [MinimizingLatenessTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/MinimizingLatenessTest.java) + * [StockProfitCalculatorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/StockProfitCalculatorTest.java) * io * [BufferedReaderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/io/BufferedReaderTest.java) * lineclipping diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/StockProfitCalculator.java b/src/main/java/com/thealgorithms/greedyalgorithms/StockProfitCalculator.java new file mode 100644 index 00000000000..01950d902b4 --- /dev/null +++ b/src/main/java/com/thealgorithms/greedyalgorithms/StockProfitCalculator.java @@ -0,0 +1,34 @@ +package com.thealgorithms.greedyalgorithms; + +/** + * The StockProfitCalculator class provides a method to calculate the maximum profit + * that can be made from a single buy and sell of one share of stock. + * The approach uses a greedy algorithm to efficiently determine the maximum profit. + * + * @author Hardvan + */ +public final class StockProfitCalculator { + private StockProfitCalculator() { + } + + /** + * Calculates the maximum profit from a list of stock prices. + * + * @param prices an array of integers representing the stock prices on different days + * @return the maximum profit that can be achieved from a single buy and sell + * transaction, or 0 if no profit can be made + */ + public static int maxProfit(int[] prices) { + if (prices == null || prices.length == 0) { + return 0; + } + + int minPrice = prices[0]; + int maxProfit = 0; + for (int price : prices) { + minPrice = Math.min(price, minPrice); + maxProfit = Math.max(price - minPrice, maxProfit); + } + return maxProfit; + } +} diff --git a/src/test/java/com/thealgorithms/greedyalgorithms/StockProfitCalculatorTest.java b/src/test/java/com/thealgorithms/greedyalgorithms/StockProfitCalculatorTest.java new file mode 100644 index 00000000000..afad76a3452 --- /dev/null +++ b/src/test/java/com/thealgorithms/greedyalgorithms/StockProfitCalculatorTest.java @@ -0,0 +1,21 @@ +package com.thealgorithms.greedyalgorithms; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class StockProfitCalculatorTest { + + @ParameterizedTest + @MethodSource("provideTestCases") + public void testMaxProfit(int[] prices, int expected) { + assertEquals(expected, StockProfitCalculator.maxProfit(prices)); + } + + private static Stream provideTestCases() { + return Stream.of(Arguments.of(new int[] {7, 1, 5, 3, 6, 4}, 5), Arguments.of(new int[] {7, 6, 4, 3, 1}, 0), Arguments.of(new int[] {5, 5, 5, 5, 5}, 0), Arguments.of(new int[] {10}, 0), Arguments.of(new int[] {1, 5}, 4), Arguments.of(new int[] {2, 4, 1, 3, 7, 5}, 6)); + } +} From dfff8d95d2fb9a86235a641b8b2fdbbb95196076 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:22:17 +0530 Subject: [PATCH 22/75] Add `MinimumWaitingTime` algorithm (#5794) --- DIRECTORY.md | 2 + .../greedyalgorithms/MinimumWaitingTime.java | 37 +++++++++++++++++++ .../MinimumWaitingTimeTest.java | 21 +++++++++++ 3 files changed, 60 insertions(+) create mode 100644 src/main/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTime.java create mode 100644 src/test/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTimeTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index aa34bf1a953..e7a08a12aba 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -320,6 +320,7 @@ * [JobSequencing](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/JobSequencing.java) * [MergeIntervals](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/MergeIntervals.java) * [MinimizingLateness](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/MinimizingLateness.java) + * [MinimumWaitingTime](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTime.java) * [StockProfitCalculator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/StockProfitCalculator.java) * io * [BufferedReader](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/io/BufferedReader.java) @@ -921,6 +922,7 @@ * [JobSequencingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/JobSequencingTest.java) * [MergeIntervalsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/MergeIntervalsTest.java) * [MinimizingLatenessTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/MinimizingLatenessTest.java) + * [MinimumWaitingTimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTimeTest.java) * [StockProfitCalculatorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/StockProfitCalculatorTest.java) * io * [BufferedReaderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/io/BufferedReaderTest.java) diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTime.java b/src/main/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTime.java new file mode 100644 index 00000000000..2341bcdee9f --- /dev/null +++ b/src/main/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTime.java @@ -0,0 +1,37 @@ +package com.thealgorithms.greedyalgorithms; + +import java.util.Arrays; + +/** + * The MinimumWaitingTime class provides a method to calculate the minimum + * waiting time for a list of queries using a greedy algorithm. + * + * @author Hardvan + */ +public final class MinimumWaitingTime { + private MinimumWaitingTime() { + } + + /** + * Calculates the minimum waiting time for a list of queries. + * The function sorts the queries in non-decreasing order and then calculates + * the waiting time for each query based on its position in the sorted list. + * + * @param queries an array of integers representing the query times in picoseconds + * @return the minimum waiting time in picoseconds + */ + public static int minimumWaitingTime(int[] queries) { + int n = queries.length; + if (n <= 1) { + return 0; + } + + Arrays.sort(queries); + + int totalWaitingTime = 0; + for (int i = 0; i < n; i++) { + totalWaitingTime += queries[i] * (n - i - 1); + } + return totalWaitingTime; + } +} diff --git a/src/test/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTimeTest.java b/src/test/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTimeTest.java new file mode 100644 index 00000000000..64cb4b80f18 --- /dev/null +++ b/src/test/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTimeTest.java @@ -0,0 +1,21 @@ +package com.thealgorithms.greedyalgorithms; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class MinimumWaitingTimeTest { + + @ParameterizedTest + @MethodSource("provideTestCases") + public void testMinimumWaitingTime(int[] queries, int expected) { + assertEquals(expected, MinimumWaitingTime.minimumWaitingTime(queries)); + } + + private static Stream provideTestCases() { + return Stream.of(Arguments.of(new int[] {3, 2, 1, 2, 6}, 17), Arguments.of(new int[] {3, 2, 1}, 4), Arguments.of(new int[] {1, 2, 3, 4}, 10), Arguments.of(new int[] {5, 5, 5, 5}, 30), Arguments.of(new int[] {}, 0)); + } +} From 169a01e0c803cc5ccfb739a85c70ee46ea228337 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 16 Oct 2024 16:59:15 +0530 Subject: [PATCH 23/75] Enhance documentation in `FractionalKnapsack` (#5795) --- .../greedyalgorithms/FractionalKnapsack.java | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/FractionalKnapsack.java b/src/main/java/com/thealgorithms/greedyalgorithms/FractionalKnapsack.java index 082bd9c68b3..9535a7c6190 100644 --- a/src/main/java/com/thealgorithms/greedyalgorithms/FractionalKnapsack.java +++ b/src/main/java/com/thealgorithms/greedyalgorithms/FractionalKnapsack.java @@ -3,39 +3,50 @@ import java.util.Arrays; import java.util.Comparator; -// Problem Link: https://en.wikipedia.org/wiki/Continuous_knapsack_problem - +/** + * The FractionalKnapsack class provides a method to solve the fractional knapsack problem + * using a greedy algorithm approach. It allows for selecting fractions of items to maximize + * the total value in a knapsack with a given weight capacity. + * + * The problem consists of a set of items, each with a weight and a value, and a knapsack + * that can carry a maximum weight. The goal is to maximize the value of items in the knapsack, + * allowing for the inclusion of fractions of items. + * + * Problem Link: https://en.wikipedia.org/wiki/Continuous_knapsack_problem + */ public final class FractionalKnapsack { private FractionalKnapsack() { } - // Function to perform fractional knapsack + + /** + * Computes the maximum value that can be accommodated in a knapsack of a given capacity. + * + * @param weight an array of integers representing the weights of the items + * @param value an array of integers representing the values of the items + * @param capacity an integer representing the maximum weight capacity of the knapsack + * @return the maximum value that can be obtained by including the items in the knapsack + */ public static int fractionalKnapsack(int[] weight, int[] value, int capacity) { - // Create a 2D array to store item indices and their value-to-weight ratios. double[][] ratio = new double[weight.length][2]; - // Populate the ratio array with item indices and their value-to-weight ratios. for (int i = 0; i < weight.length; i++) { - ratio[i][0] = i; // Assign item index. - ratio[i][1] = value[i] / (double) weight[i]; // Calculate and assign value-to-weight ratio. + ratio[i][0] = i; + ratio[i][1] = value[i] / (double) weight[i]; } - // Sort items by their value-to-weight ratios in descending order. Arrays.sort(ratio, Comparator.comparingDouble(o -> o[1])); - int finalValue = 0; // Variable to store the final knapsack value. - double current = capacity; // Variable to track the remaining capacity of the knapsack. + int finalValue = 0; + double current = capacity; - // Iterate through the sorted items to select items for the knapsack. for (int i = ratio.length - 1; i >= 0; i--) { - int index = (int) ratio[i][0]; // Get the item index. + int index = (int) ratio[i][0]; if (current >= weight[index]) { - // If the entire item can fit in the knapsack, add its value. finalValue += value[index]; current -= weight[index]; } else { - // If only a fraction of the item can fit, add a proportionate value. finalValue += (int) (ratio[i][1] * current); - break; // Stop adding items to the knapsack since it's full. + break; } } return finalValue; From 33dee073d08baedae02e6da5d9b672ba3c11813a Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 16 Oct 2024 17:46:32 +0530 Subject: [PATCH 24/75] Add `AssignmentUsingBitmask` algorithm (#5792) --- DIRECTORY.md | 2 + .../AssignmentUsingBitmask.java | 91 +++++++++++++++++++ .../AssignmentUsingBitmaskTest.java | 54 +++++++++++ 3 files changed, 147 insertions(+) create mode 100644 src/main/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmask.java create mode 100644 src/test/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmaskTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index e7a08a12aba..e511ab40b32 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -262,6 +262,7 @@ * [TilingProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/TilingProblem.java) * dynamicprogramming * [Abbreviation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/Abbreviation.java) + * [AssignmentUsingBitmask](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmask.java) * [BoardPath](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/BoardPath.java) * [BoundaryFill](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/BoundaryFill.java) * [BruteForceKnapsack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsack.java) @@ -866,6 +867,7 @@ * [TilingProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/TilingProblemTest.java) * dynamicprogramming * [AbbreviationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/AbbreviationTest.java) + * [AssignmentUsingBitmaskTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmaskTest.java) * [BoardPathTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/BoardPathTest.java) * [BoundaryFillTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/BoundaryFillTest.java) * [BruteForceKnapsackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsackTest.java) diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmask.java b/src/main/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmask.java new file mode 100644 index 00000000000..5a894ca004b --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmask.java @@ -0,0 +1,91 @@ +package com.thealgorithms.dynamicprogramming; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * The AssignmentUsingBitmask class is used to calculate the total number of ways + * tasks can be distributed among people, given specific constraints on who can perform which tasks. + * The approach uses bitmasking and dynamic programming to efficiently solve the problem. + * + * @author Hardvan + */ +public final class AssignmentUsingBitmask { + + private final int totalTasks; + private final int[][] dp; + private final List> task; + private final int finalMask; + + /** + * Constructor for the AssignmentUsingBitmask class. + * + * @param taskPerformed a list of lists, where each inner list contains the tasks that a person can perform. + * @param total the total number of tasks. + */ + public AssignmentUsingBitmask(List> taskPerformed, int total) { + this.totalTasks = total; + this.dp = new int[1 << taskPerformed.size()][total + 1]; + for (int[] row : dp) { + Arrays.fill(row, -1); + } + + this.task = new ArrayList<>(totalTasks + 1); + for (int i = 0; i <= totalTasks; i++) { + this.task.add(new ArrayList<>()); + } + + // Final mask to check if all persons are included + this.finalMask = (1 << taskPerformed.size()) - 1; + + // Fill the task list + for (int i = 0; i < taskPerformed.size(); i++) { + for (int j : taskPerformed.get(i)) { + this.task.get(j).add(i); + } + } + } + + /** + * Counts the ways to assign tasks until the given task number with the specified mask. + * + * @param mask the bitmask representing the current state of assignments. + * @param taskNo the current task number being processed. + * @return the number of ways to assign tasks. + */ + private int countWaysUntil(int mask, int taskNo) { + if (mask == finalMask) { + return 1; + } + if (taskNo > totalTasks) { + return 0; + } + if (dp[mask][taskNo] != -1) { + return dp[mask][taskNo]; + } + + int totalWays = countWaysUntil(mask, taskNo + 1); + + // Assign tasks to all possible persons + for (int p : task.get(taskNo)) { + // If the person is already assigned a task + if ((mask & (1 << p)) != 0) { + continue; + } + totalWays += countWaysUntil(mask | (1 << p), taskNo + 1); + } + + dp[mask][taskNo] = totalWays; + return dp[mask][taskNo]; + } + + /** + * Counts the total number of ways to distribute tasks among persons. + * + * @return the total number of ways to distribute tasks. + */ + public int countNoOfWays() { + return countWaysUntil(0, 1); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmaskTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmaskTest.java new file mode 100644 index 00000000000..0258f395051 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmaskTest.java @@ -0,0 +1,54 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public final class AssignmentUsingBitmaskTest { + + @Test + public void testCountNoOfWays() { + int totalTasks = 5; + + List> taskPerformed = Arrays.asList(Arrays.asList(1, 3, 4), Arrays.asList(1, 2, 5), Arrays.asList(3, 4)); + + AssignmentUsingBitmask assignment = new AssignmentUsingBitmask(taskPerformed, totalTasks); + int ways = assignment.countNoOfWays(); + assertEquals(10, ways); + } + + @Test + public void testNoPossibleAssignments() { + int totalTasks = 3; + + List> taskPerformed = Arrays.asList(Arrays.asList(2), Arrays.asList(3)); + + AssignmentUsingBitmask assignment = new AssignmentUsingBitmask(taskPerformed, totalTasks); + int ways = assignment.countNoOfWays(); + assertEquals(1, ways); + } + + @Test + public void testSinglePersonMultipleTasks() { + int totalTasks = 3; + + List> taskPerformed = Arrays.asList(Arrays.asList(1, 2, 3)); + + AssignmentUsingBitmask assignment = new AssignmentUsingBitmask(taskPerformed, totalTasks); + int ways = assignment.countNoOfWays(); + assertEquals(3, ways); + } + + @Test + public void testMultiplePeopleSingleTask() { + int totalTasks = 1; + + List> taskPerformed = Arrays.asList(Arrays.asList(1), Arrays.asList(1)); + + AssignmentUsingBitmask assignment = new AssignmentUsingBitmask(taskPerformed, totalTasks); + int ways = assignment.countNoOfWays(); + assertEquals(0, ways); + } +} From e499d3b63e9cb3dc711ddd5d4a2fc3faba9b3d46 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 19 Oct 2024 12:27:26 +0000 Subject: [PATCH 25/75] Bump org.mockito:mockito-core from 5.14.1 to 5.14.2 (#5856) Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.14.1 to 5.14.2. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v5.14.1...v5.14.2) --- updated-dependencies: - dependency-name: org.mockito:mockito-core dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alex Klymenko --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 812f46c700e..19357d7c8b1 100644 --- a/pom.xml +++ b/pom.xml @@ -43,7 +43,7 @@ org.mockito mockito-core - 5.14.1 + 5.14.2 test From 7f21f2d316b5a7df485df69a76fba69f15b1dc23 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Oct 2024 11:07:55 +0300 Subject: [PATCH 26/75] Bump org.junit:junit-bom from 5.11.2 to 5.11.3 (#5934) Bumps [org.junit:junit-bom](https://github.com/junit-team/junit5) from 5.11.2 to 5.11.3. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.11.2...r5.11.3) --- updated-dependencies: - dependency-name: org.junit:junit-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 19357d7c8b1..45354fc781f 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ org.junit junit-bom - 5.11.2 + 5.11.3 pom import From cff79d443952a486b4b7cd70916878adfe70a26a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Oct 2024 11:11:41 +0300 Subject: [PATCH 27/75] Bump org.junit.jupiter:junit-jupiter from 5.11.2 to 5.11.3 (#5935) Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.11.2 to 5.11.3. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.11.2...r5.11.3) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alex Klymenko --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 45354fc781f..2d6512fd157 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ org.junit.jupiter junit-jupiter - 5.11.2 + 5.11.3 test From ae37b5f152b52b294cc4fbd83cd30d045afaaef5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Oct 2024 11:35:26 +0300 Subject: [PATCH 28/75] Bump com.github.spotbugs:spotbugs-maven-plugin from 4.8.6.4 to 4.8.6.5 (#5936) Bumps [com.github.spotbugs:spotbugs-maven-plugin](https://github.com/spotbugs/spotbugs-maven-plugin) from 4.8.6.4 to 4.8.6.5. - [Release notes](https://github.com/spotbugs/spotbugs-maven-plugin/releases) - [Commits](https://github.com/spotbugs/spotbugs-maven-plugin/compare/spotbugs-maven-plugin-4.8.6.4...spotbugs-maven-plugin-4.8.6.5) --- updated-dependencies: - dependency-name: com.github.spotbugs:spotbugs-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alex Klymenko --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2d6512fd157..1b70c0a83f1 100644 --- a/pom.xml +++ b/pom.xml @@ -132,7 +132,7 @@ com.github.spotbugs spotbugs-maven-plugin - 4.8.6.4 + 4.8.6.5 spotbugs-exclude.xml true From c440c1d69e65f7188b2080e350dc9a53c624c523 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Oct 2024 11:39:55 +0300 Subject: [PATCH 29/75] Bump org.junit.jupiter:junit-jupiter-api from 5.11.2 to 5.11.3 (#5937) Bumps [org.junit.jupiter:junit-jupiter-api](https://github.com/junit-team/junit5) from 5.11.2 to 5.11.3. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.11.2...r5.11.3) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter-api dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alex Klymenko --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1b70c0a83f1..ec42ffc3c86 100644 --- a/pom.xml +++ b/pom.xml @@ -51,7 +51,7 @@ org.junit.jupiter junit-jupiter-api - 5.11.2 + 5.11.3 test From 69a142441542378083afeb974dbd46fd66ff2ec7 Mon Sep 17 00:00:00 2001 From: ANANT JAIN <139585700+anant-jain01@users.noreply.github.com> Date: Tue, 22 Oct 2024 23:02:51 +0530 Subject: [PATCH 30/75] Add StalinSort (#5738) --- .../sorts/AdaptiveMergeSort.java | 40 ++++++++++++++ .../com/thealgorithms/sorts/StalinSort.java | 21 ++++++++ .../sorts/AdaptiveMergeSortTest.java | 53 +++++++++++++++++++ .../thealgorithms/sorts/StalinSortTest.java | 53 +++++++++++++++++++ 4 files changed, 167 insertions(+) create mode 100644 src/main/java/com/thealgorithms/sorts/AdaptiveMergeSort.java create mode 100644 src/main/java/com/thealgorithms/sorts/StalinSort.java create mode 100644 src/test/java/com/thealgorithms/sorts/AdaptiveMergeSortTest.java create mode 100644 src/test/java/com/thealgorithms/sorts/StalinSortTest.java diff --git a/src/main/java/com/thealgorithms/sorts/AdaptiveMergeSort.java b/src/main/java/com/thealgorithms/sorts/AdaptiveMergeSort.java new file mode 100644 index 00000000000..2c71bae8b55 --- /dev/null +++ b/src/main/java/com/thealgorithms/sorts/AdaptiveMergeSort.java @@ -0,0 +1,40 @@ +package com.thealgorithms.sorts; + +public class AdaptiveMergeSort implements SortAlgorithm { + @SuppressWarnings("unchecked") + public > T[] sort(T[] array) { + if (array.length <= 1) { + return array; + } + T[] aux = array.clone(); + sort(array, aux, 0, array.length - 1); + return array; + } + + private > void sort(T[] array, T[] aux, int low, int high) { + if (low >= high) { + return; + } + int mid = low + (high - low) / 2; + sort(array, aux, low, mid); + sort(array, aux, mid + 1, high); + merge(array, aux, low, mid, high); + } + + private > void merge(T[] array, T[] aux, int low, int mid, int high) { + System.arraycopy(array, low, aux, low, high - low + 1); + int i = low; + int j = mid + 1; + for (int k = low; k <= high; k++) { + if (i > mid) { + array[k] = aux[j++]; + } else if (j > high) { + array[k] = aux[i++]; + } else if (aux[j].compareTo(aux[i]) < 0) { + array[k] = aux[j++]; + } else { + array[k] = aux[i++]; + } + } + } +} diff --git a/src/main/java/com/thealgorithms/sorts/StalinSort.java b/src/main/java/com/thealgorithms/sorts/StalinSort.java new file mode 100644 index 00000000000..5aaf530fd94 --- /dev/null +++ b/src/main/java/com/thealgorithms/sorts/StalinSort.java @@ -0,0 +1,21 @@ +package com.thealgorithms.sorts; + +public class StalinSort implements SortAlgorithm { + @SuppressWarnings("unchecked") + public > T[] sort(T[] array) { + if (array.length == 0) { + return array; + } + int currentIndex = 0; + for (int i = 1; i < array.length; i++) { + if (array[i].compareTo(array[currentIndex]) >= 0) { + currentIndex++; + array[currentIndex] = array[i]; + } + } + // Create a result array with sorted elements + T[] result = (T[]) java.lang.reflect.Array.newInstance(array.getClass().getComponentType(), currentIndex + 1); + System.arraycopy(array, 0, result, 0, currentIndex + 1); + return result; + } +} diff --git a/src/test/java/com/thealgorithms/sorts/AdaptiveMergeSortTest.java b/src/test/java/com/thealgorithms/sorts/AdaptiveMergeSortTest.java new file mode 100644 index 00000000000..9d94b165d81 --- /dev/null +++ b/src/test/java/com/thealgorithms/sorts/AdaptiveMergeSortTest.java @@ -0,0 +1,53 @@ +package com.thealgorithms.sorts; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.Test; + +public class AdaptiveMergeSortTest { + + @Test + public void testSortIntegers() { + AdaptiveMergeSort adaptiveMergeSort = new AdaptiveMergeSort(); + Integer[] input = {4, 23, 6, 78, 1, 54, 231, 9, 12}; + Integer[] expected = {1, 4, 6, 9, 12, 23, 54, 78, 231}; + Integer[] result = adaptiveMergeSort.sort(input); + assertArrayEquals(expected, result); + } + + @Test + public void testSortStrings() { + AdaptiveMergeSort adaptiveMergeSort = new AdaptiveMergeSort(); + String[] input = {"c", "a", "e", "b", "d"}; + String[] expected = {"a", "b", "c", "d", "e"}; + String[] result = adaptiveMergeSort.sort(input); + assertArrayEquals(expected, result); + } + + @Test + public void testSortWithDuplicates() { + AdaptiveMergeSort adaptiveMergeSort = new AdaptiveMergeSort(); + Integer[] input = {1, 3, 2, 2, 5, 4}; + Integer[] expected = {1, 2, 2, 3, 4, 5}; + Integer[] result = adaptiveMergeSort.sort(input); + assertArrayEquals(expected, result); + } + + @Test + public void testSortEmptyArray() { + AdaptiveMergeSort adaptiveMergeSort = new AdaptiveMergeSort(); + Integer[] input = {}; + Integer[] expected = {}; + Integer[] result = adaptiveMergeSort.sort(input); + assertArrayEquals(expected, result); + } + + @Test + public void testSortSingleElement() { + AdaptiveMergeSort adaptiveMergeSort = new AdaptiveMergeSort(); + Integer[] input = {42}; + Integer[] expected = {42}; + Integer[] result = adaptiveMergeSort.sort(input); + assertArrayEquals(expected, result); + } +} diff --git a/src/test/java/com/thealgorithms/sorts/StalinSortTest.java b/src/test/java/com/thealgorithms/sorts/StalinSortTest.java new file mode 100644 index 00000000000..ee9c6137952 --- /dev/null +++ b/src/test/java/com/thealgorithms/sorts/StalinSortTest.java @@ -0,0 +1,53 @@ +package com.thealgorithms.sorts; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.Test; + +public class StalinSortTest { + + @Test + public void testSortIntegers() { + StalinSort stalinSort = new StalinSort(); + Integer[] input = {4, 23, 6, 78, 1, 54, 231, 9, 12}; + Integer[] expected = {4, 23, 78, 231}; + Integer[] result = stalinSort.sort(input); + assertArrayEquals(expected, result); + } + + @Test + public void testSortStrings() { + StalinSort stalinSort = new StalinSort(); + String[] input = {"c", "a", "e", "b", "d"}; + String[] expected = {"c", "e"}; + String[] result = stalinSort.sort(input); + assertArrayEquals(expected, result); + } + + @Test + public void testSortWithDuplicates() { + StalinSort stalinSort = new StalinSort(); + Integer[] input = {1, 3, 2, 2, 5, 4}; + Integer[] expected = {1, 3, 5}; + Integer[] result = stalinSort.sort(input); + assertArrayEquals(expected, result); + } + + @Test + public void testSortEmptyArray() { + StalinSort stalinSort = new StalinSort(); + Integer[] input = {}; + Integer[] expected = {}; + Integer[] result = stalinSort.sort(input); + assertArrayEquals(expected, result); + } + + @Test + public void testSortSingleElement() { + StalinSort stalinSort = new StalinSort(); + Integer[] input = {42}; + Integer[] expected = {42}; + Integer[] result = stalinSort.sort(input); + assertArrayEquals(expected, result); + } +} From efb16c1eff0153ee81da7b6aafca1932f36dd722 Mon Sep 17 00:00:00 2001 From: PANKAJ PATWAL <120747214+Chiefpatwal@users.noreply.github.com> Date: Tue, 22 Oct 2024 23:20:47 +0530 Subject: [PATCH 31/75] Add edge case to handle negative rod length in RodCutting algorithm (#5904) --- .../com/thealgorithms/dynamicprogramming/RodCutting.java | 4 ++++ .../thealgorithms/dynamicprogramming/RodCuttingTest.java | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/RodCutting.java b/src/main/java/com/thealgorithms/dynamicprogramming/RodCutting.java index 76b341e2c82..6d33826b6b1 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/RodCutting.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/RodCutting.java @@ -22,6 +22,10 @@ public static int cutRod(int[] price, int n) { if (price == null || price.length == 0) { throw new IllegalArgumentException("Price array cannot be null or empty."); } + if (n < 0) { + throw new IllegalArgumentException("Rod length cannot be negative."); + } + // Create an array to store the maximum obtainable values for each rod length. int[] val = new int[n + 1]; val[0] = 0; diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/RodCuttingTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/RodCuttingTest.java index 39497a76839..9cf21fd836d 100644 --- a/src/test/java/com/thealgorithms/dynamicprogramming/RodCuttingTest.java +++ b/src/test/java/com/thealgorithms/dynamicprogramming/RodCuttingTest.java @@ -93,4 +93,10 @@ void testCutRodEmptyPrices() { int length = 5; assertThrows(IllegalArgumentException.class, () -> RodCutting.cutRod(prices, length), "An empty prices array should throw an IllegalArgumentException."); } + @Test + void testCutRodNegativeLength() { + int[] prices = {1, 5, 8, 9, 10}; // Prices are irrelevant for negative length + int length = -1; + assertThrows(IllegalArgumentException.class, () -> RodCutting.cutRod(prices, length), "A negative rod length should throw an IllegalArgumentException."); + } } From ef72b1e40b4be91ed7489c2d572872af4ea3c3fd Mon Sep 17 00:00:00 2001 From: Tanmay Singh <156223260+Tanmay-Singh3004@users.noreply.github.com> Date: Tue, 22 Oct 2024 23:24:45 +0530 Subject: [PATCH 32/75] Remove main function, improve docstring, add JUnit tests for `KrishnamurthyNumber`. (#5881) --- DIRECTORY.md | 1 + .../maths/KrishnamurthyNumber.java | 52 +++++++--------- .../maths/KrishnamurthyNumberTest.java | 62 +++++++++++++++++++ 3 files changed, 87 insertions(+), 28 deletions(-) create mode 100644 src/test/java/com/thealgorithms/maths/KrishnamurthyNumberTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index e511ab40b32..3d5b2bf61d6 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -982,6 +982,7 @@ * [JosephusProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/JosephusProblemTest.java) * [KaprekarNumbersTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/KaprekarNumbersTest.java) * [KaratsubaMultiplicationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/KaratsubaMultiplicationTest.java) + * [KrishnamurthyNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/KrishnamurthyNumberTest.java) * [LeastCommonMultipleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/LeastCommonMultipleTest.java) * [LeonardoNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/LeonardoNumberTest.java) * [LiouvilleLambdaFunctionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/LiouvilleLambdaFunctionTest.java) diff --git a/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java b/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java index f5ff50337bc..03f18aca786 100644 --- a/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java +++ b/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java @@ -1,31 +1,38 @@ package com.thealgorithms.maths; -/* This is a program to check if a number is a Krishnamurthy number or not. -A number is a Krishnamurthy number if the sum of the factorials of the digits of the number is equal -to the number itself. For example, 1, 2 and 145 are Krishnamurthy numbers. Krishnamurthy number is -also referred to as a Strong number. +/** + * Utility class for checking if a number is a Krishnamurthy number. + * + * A Krishnamurthy number (also known as a Strong number) is a number whose sum of the factorials of its digits is equal to the number itself. + * + * For example, 145 is a Krishnamurthy number because 1! + 4! + 5! = 1 + 24 + 120 = 145. + * Example usage: + *
+ * boolean isKrishnamurthy = KrishnamurthyNumber.isKrishnamurthy(145);
+ * System.out.println(isKrishnamurthy); // Output: true
+ *
+ * isKrishnamurthy = KrishnamurthyNumber.isKrishnamurthy(123);
+ * System.out.println(isKrishnamurthy); // Output: false
+ * 
*/ -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; - public final class KrishnamurthyNumber { + private KrishnamurthyNumber() { } - // returns True if the number is a Krishnamurthy number and False if it is not. - - public static boolean isKMurthy(int n) { - // initialising the variable s that will store the sum of the factorials of the digits to 0 - int s = 0; - // storing the number n in a temporary variable tmp + /** + * Checks if a number is a Krishnamurthy number. + * + * @param n The number to check + * @return true if the number is a Krishnamurthy number, false otherwise + */ + public static boolean isKrishnamurthy(int n) { int tmp = n; + int s = 0; - // Krishnamurthy numbers are positive if (n <= 0) { return false; - } // checking if the number is a Krishnamurthy number - else { + } else { while (n != 0) { // initialising the variable fact that will store the factorials of the digits int fact = 1; @@ -43,15 +50,4 @@ public static boolean isKMurthy(int n) { return tmp == s; } } - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - System.out.println("Enter a number to check if it is a Krishnamurthy number: "); - int n = Integer.parseInt(br.readLine()); - if (isKMurthy(n)) { - System.out.println(n + " is a Krishnamurthy number."); - } else { - System.out.println(n + " is NOT a Krishnamurthy number."); - } - } } diff --git a/src/test/java/com/thealgorithms/maths/KrishnamurthyNumberTest.java b/src/test/java/com/thealgorithms/maths/KrishnamurthyNumberTest.java new file mode 100644 index 00000000000..595acde2b5d --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/KrishnamurthyNumberTest.java @@ -0,0 +1,62 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the KrishnamurthyNumber class. + */ +public class KrishnamurthyNumberTest { + + /** + * Test the isKrishnamurthy method with a known Krishnamurthy number. + */ + @Test + public void testIsKrishnamurthyTrue() { + assertTrue(KrishnamurthyNumber.isKrishnamurthy(145)); + } + + /** + * Test the isKrishnamurthy method with a number that is not a Krishnamurthy number. + */ + @Test + public void testIsKrishnamurthyFalse() { + assertFalse(KrishnamurthyNumber.isKrishnamurthy(123)); + } + + /** + * Test the isKrishnamurthy method with zero. + */ + @Test + public void testIsKrishnamurthyZero() { + assertFalse(KrishnamurthyNumber.isKrishnamurthy(0)); + } + + /** + * Test the isKrishnamurthy method with a negative number. + */ + @Test + public void testIsKrishnamurthyNegative() { + assertFalse(KrishnamurthyNumber.isKrishnamurthy(-145)); + } + + /** + * Test the isKrishnamurthy method with a single-digit Krishnamurthy number. + */ + @Test + public void testIsKrishnamurthySingleDigitTrue() { + assertTrue(KrishnamurthyNumber.isKrishnamurthy(1)); + assertTrue(KrishnamurthyNumber.isKrishnamurthy(2)); + } + + /** + * Test the isKrishnamurthy method with a single-digit number that is not a Krishnamurthy number. + */ + @Test + public void testIsKrishnamurthySingleDigitFalse() { + assertFalse(KrishnamurthyNumber.isKrishnamurthy(3)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(4)); + } +} From 0bb22fbc7da5358993c6d8613079ab33fd370e0a Mon Sep 17 00:00:00 2001 From: Tanmay Singh <156223260+Tanmay-Singh3004@users.noreply.github.com> Date: Tue, 22 Oct 2024 23:34:00 +0530 Subject: [PATCH 33/75] Remove main function from GCD class (#5828) --- .../java/com/thealgorithms/maths/GCD.java | 28 +++++++++++-------- .../java/com/thealgorithms/maths/GCDTest.java | 5 ++++ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/thealgorithms/maths/GCD.java b/src/main/java/com/thealgorithms/maths/GCD.java index 5156e4ac881..df27516367b 100644 --- a/src/main/java/com/thealgorithms/maths/GCD.java +++ b/src/main/java/com/thealgorithms/maths/GCD.java @@ -1,9 +1,23 @@ package com.thealgorithms.maths; /** - * This is Euclid's algorithm, used to find the greatest common - * denominator Override function name gcd + * This class provides methods to compute the Greatest Common Divisor (GCD) of two or more integers. * + * The Greatest Common Divisor (GCD) of two or more integers is the largest positive integer that divides each of the integers without leaving a remainder. + * + * The GCD can be computed using the Euclidean algorithm, which is based on the principle that the GCD of two numbers also divides their difference. + * + * For more information, refer to the + * Greatest Common Divisor Wikipedia page. + * + * Example usage: + *
+ * int result1 = GCD.gcd(48, 18);
+ * System.out.println("GCD of 48 and 18: " + result1); // Output: 6
+ *
+ * int result2 = GCD.gcd(48, 18, 30);
+ * System.out.println("GCD of 48, 18, and 30: " + result2); // Output: 6
+ * 
* @author Oskar Enmalm 3/10/17 */ public final class GCD { @@ -40,7 +54,7 @@ public static int gcd(int num1, int num2) { * @param numbers the input array * @return gcd of all of the numbers in the input array */ - public static int gcd(int[] numbers) { + public static int gcd(int... numbers) { int result = 0; for (final var number : numbers) { result = gcd(result, number); @@ -48,12 +62,4 @@ public static int gcd(int[] numbers) { return result; } - - public static void main(String[] args) { - int[] myIntArray = {4, 16, 32}; - - // call gcd function (input array) - System.out.println(gcd(myIntArray)); // => 4 - System.out.printf("gcd(40,24)=%d gcd(24,40)=%d%n", gcd(40, 24), gcd(24, 40)); // => 8 - } } diff --git a/src/test/java/com/thealgorithms/maths/GCDTest.java b/src/test/java/com/thealgorithms/maths/GCDTest.java index 5a659664fd2..bac3f8f7596 100644 --- a/src/test/java/com/thealgorithms/maths/GCDTest.java +++ b/src/test/java/com/thealgorithms/maths/GCDTest.java @@ -40,6 +40,11 @@ void test7() { Assertions.assertEquals(GCD.gcd(9, 6), 3); } + @Test + void test8() { + Assertions.assertEquals(GCD.gcd(48, 18, 30, 12), 6); + } + @Test void testArrayGcd1() { Assertions.assertEquals(GCD.gcd(new int[] {9, 6}), 3); From 5a1f681234c45e380cbeb76bb81027acffeb791b Mon Sep 17 00:00:00 2001 From: Manish Raj <2200032955@kluniversity.in> Date: Tue, 22 Oct 2024 23:37:19 +0530 Subject: [PATCH 34/75] Optimize BreadthFirstSearch implementation (#5882) --- .../searches/BreadthFirstSearch.java | 52 +++++++++++++------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/thealgorithms/searches/BreadthFirstSearch.java b/src/main/java/com/thealgorithms/searches/BreadthFirstSearch.java index debab98c67a..7ac9c7b0152 100644 --- a/src/main/java/com/thealgorithms/searches/BreadthFirstSearch.java +++ b/src/main/java/com/thealgorithms/searches/BreadthFirstSearch.java @@ -3,37 +3,54 @@ import com.thealgorithms.datastructures.Node; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Queue; +import java.util.Set; /** - * @author: caos321 - * @date: 31 October 2021 (Sunday) - * @wiki: https://en.wikipedia.org/wiki/Breadth-first_search + * Breadth-First Search implementation for tree/graph traversal. + * @author caos321 + * @co-author @manishraj27 + * @see Breadth-first search */ public class BreadthFirstSearch { - private final List visited = new ArrayList<>(); + private final Set visitedSet = new HashSet<>(); - public Optional> search(final Node node, final T value) { - if (node == null) { + /** + * Performs a breadth-first search to find a node with the given value. + * + * @param root The root node to start the search from + * @param value The value to search for + * @return Optional containing the found node, or empty if not found + */ + public Optional> search(final Node root, final T value) { + if (root == null) { return Optional.empty(); } - if (node.getValue().equals(value)) { - // add root node to visited - visited.add(value); - return Optional.of(node); - } - visited.add(node.getValue()); - Queue> queue = new ArrayDeque<>(node.getChildren()); + visited.add(root.getValue()); + visitedSet.add(root.getValue()); + + if (root.getValue() == value) { + return Optional.of(root); + } + Queue> queue = new ArrayDeque<>(root.getChildren()); while (!queue.isEmpty()) { final Node current = queue.poll(); - visited.add(current.getValue()); + T currentValue = current.getValue(); + + if (visitedSet.contains(currentValue)) { + continue; + } + + visited.add(currentValue); + visitedSet.add(currentValue); - if (current.getValue().equals(value)) { + if (currentValue == value || (value != null && value.equals(currentValue))) { return Optional.of(current); } @@ -43,6 +60,11 @@ public Optional> search(final Node node, final T value) { return Optional.empty(); } + /** + * Returns the list of nodes in the order they were visited. + * + * @return List containing the visited nodes + */ public List getVisited() { return visited; } From c56d282ae092810327bc18b6083d552a91b9eb8e Mon Sep 17 00:00:00 2001 From: Ritisha Pande <96540421+riti2601@users.noreply.github.com> Date: Tue, 22 Oct 2024 23:46:35 +0530 Subject: [PATCH 35/75] Add DiffieHellman and MonoAlphabetic (#5508) --- .../thealgorithms/ciphers/DiffieHellman.java | 36 ++++++++++++++ .../thealgorithms/ciphers/MonoAlphabetic.java | 48 +++++++++++++++++++ .../ciphers/DiffieHellmanTest.java | 38 +++++++++++++++ .../ciphers/MonoAlphabeticTest.java | 29 +++++++++++ 4 files changed, 151 insertions(+) create mode 100644 src/main/java/com/thealgorithms/ciphers/DiffieHellman.java create mode 100644 src/main/java/com/thealgorithms/ciphers/MonoAlphabetic.java create mode 100644 src/test/java/com/thealgorithms/ciphers/DiffieHellmanTest.java create mode 100644 src/test/java/com/thealgorithms/ciphers/MonoAlphabeticTest.java diff --git a/src/main/java/com/thealgorithms/ciphers/DiffieHellman.java b/src/main/java/com/thealgorithms/ciphers/DiffieHellman.java new file mode 100644 index 00000000000..7470b40e001 --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/DiffieHellman.java @@ -0,0 +1,36 @@ +package com.thealgorithms.ciphers; + +import java.math.BigInteger; + +public final class DiffieHellman { + + private final BigInteger base; + private final BigInteger secret; + private final BigInteger prime; + + // Constructor to initialize base, secret, and prime + public DiffieHellman(BigInteger base, BigInteger secret, BigInteger prime) { + // Check for non-null and positive values + if (base == null || secret == null || prime == null || base.signum() <= 0 || secret.signum() <= 0 || prime.signum() <= 0) { + throw new IllegalArgumentException("Base, secret, and prime must be non-null and positive values."); + } + this.base = base; + this.secret = secret; + this.prime = prime; + } + + // Method to calculate public value (g^x mod p) + public BigInteger calculatePublicValue() { + // Returns g^x mod p + return base.modPow(secret, prime); + } + + // Method to calculate the shared secret key (otherPublic^secret mod p) + public BigInteger calculateSharedSecret(BigInteger otherPublicValue) { + if (otherPublicValue == null || otherPublicValue.signum() <= 0) { + throw new IllegalArgumentException("Other public value must be non-null and positive."); + } + // Returns b^x mod p or a^y mod p + return otherPublicValue.modPow(secret, prime); + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/MonoAlphabetic.java b/src/main/java/com/thealgorithms/ciphers/MonoAlphabetic.java new file mode 100644 index 00000000000..1d5b7110a6f --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/MonoAlphabetic.java @@ -0,0 +1,48 @@ +package com.thealgorithms.ciphers; + +public final class MonoAlphabetic { + + private MonoAlphabetic() { + throw new UnsupportedOperationException("Utility class"); + } + + // Encryption method + public static String encrypt(String data, String key) { + if (!data.matches("[A-Z]+")) { + throw new IllegalArgumentException("Input data contains invalid characters. Only uppercase A-Z are allowed."); + } + StringBuilder sb = new StringBuilder(); + + // Encrypt each character + for (char c : data.toCharArray()) { + int idx = charToPos(c); // Get the index of the character + sb.append(key.charAt(idx)); // Map to the corresponding character in the key + } + return sb.toString(); + } + + // Decryption method + public static String decrypt(String data, String key) { + StringBuilder sb = new StringBuilder(); + + // Decrypt each character + for (char c : data.toCharArray()) { + int idx = key.indexOf(c); // Find the index of the character in the key + if (idx == -1) { + throw new IllegalArgumentException("Input data contains invalid characters."); + } + sb.append(posToChar(idx)); // Convert the index back to the original character + } + return sb.toString(); + } + + // Helper method: Convert a character to its position in the alphabet + private static int charToPos(char c) { + return c - 'A'; // Subtract 'A' to get position (0 for A, 1 for B, etc.) + } + + // Helper method: Convert a position in the alphabet to a character + private static char posToChar(int pos) { + return (char) (pos + 'A'); // Add 'A' to convert position back to character + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/DiffieHellmanTest.java b/src/test/java/com/thealgorithms/ciphers/DiffieHellmanTest.java new file mode 100644 index 00000000000..6255ad22ab5 --- /dev/null +++ b/src/test/java/com/thealgorithms/ciphers/DiffieHellmanTest.java @@ -0,0 +1,38 @@ +package com.thealgorithms.ciphers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.math.BigInteger; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class DiffieHellmanTest { + + // Test for public value calculation using instance methods + @ParameterizedTest + @MethodSource("provideTestData") + public void testCalculatePublicValue(BigInteger base, BigInteger secret, BigInteger prime, BigInteger publicExpected, BigInteger sharedExpected) { + DiffieHellman dh = new DiffieHellman(base, secret, prime); // Create an instance of DiffieHellman + assertEquals(publicExpected, dh.calculatePublicValue()); // Call instance method + } + + // Test for shared secret calculation using instance methods + @ParameterizedTest + @MethodSource("provideTestData") + public void testCalculateSharedSecret(BigInteger base, BigInteger secret, BigInteger prime, BigInteger publicExpected, BigInteger sharedExpected) { + DiffieHellman dh = new DiffieHellman(base, secret, prime); // Create an instance of DiffieHellman + assertEquals(sharedExpected, dh.calculateSharedSecret(publicExpected)); // Call instance method + } + + // Provide test data for both public key and shared secret calculation + private static Stream provideTestData() { + return Stream.of(createTestArgs(5, 6, 23, 8, 13), createTestArgs(2, 5, 13, 6, 2)); + } + + // Helper method for arguments + private static Arguments createTestArgs(long base, long secret, long prime, long publicExpected, long sharedExpected) { + return Arguments.of(BigInteger.valueOf(base), BigInteger.valueOf(secret), BigInteger.valueOf(prime), BigInteger.valueOf(publicExpected), BigInteger.valueOf(sharedExpected)); + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/MonoAlphabeticTest.java b/src/test/java/com/thealgorithms/ciphers/MonoAlphabeticTest.java new file mode 100644 index 00000000000..b1a8c78c952 --- /dev/null +++ b/src/test/java/com/thealgorithms/ciphers/MonoAlphabeticTest.java @@ -0,0 +1,29 @@ +package com.thealgorithms.ciphers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class MonoAlphabeticTest { + + // Test for both encryption and decryption with different keys + @ParameterizedTest + @MethodSource("provideTestData") + public void testEncryptDecrypt(String plainText, String key, String encryptedText) { + // Test encryption + String actualEncrypted = MonoAlphabetic.encrypt(plainText, key); + assertEquals(encryptedText, actualEncrypted, "Encryption failed for input: " + plainText + " with key: " + key); + + // Test decryption + String actualDecrypted = MonoAlphabetic.decrypt(encryptedText, key); + assertEquals(plainText, actualDecrypted, "Decryption failed for input: " + encryptedText + " with key: " + key); + } + + // Provide test data for both encryption and decryption + private static Stream provideTestData() { + return Stream.of(Arguments.of("HELLO", "MNBVCXZLKJHGFDSAPOIUYTREWQ", "LCGGS"), Arguments.of("JAVA", "MNBVCXZLKJHGFDSAPOIUYTREWQ", "JMTM"), Arguments.of("HELLO", "QWERTYUIOPLKJHGFDSAZXCVBNM", "ITKKG"), Arguments.of("JAVA", "QWERTYUIOPLKJHGFDSAZXCVBNM", "PQCQ")); + } +} From 87030aff1eb7b7a41d61cf8395571f91f69ffdc3 Mon Sep 17 00:00:00 2001 From: Benjamin Burstein <98127047+bennybebo@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:30:37 -0400 Subject: [PATCH 36/75] Add BaconianCipher (#5932) --- .../thealgorithms/ciphers/BaconianCipher.java | 71 +++++++++++++++++++ .../ciphers/BaconianCipherTest.java | 34 +++++++++ 2 files changed, 105 insertions(+) create mode 100644 src/main/java/com/thealgorithms/ciphers/BaconianCipher.java create mode 100644 src/test/java/com/thealgorithms/ciphers/BaconianCipherTest.java diff --git a/src/main/java/com/thealgorithms/ciphers/BaconianCipher.java b/src/main/java/com/thealgorithms/ciphers/BaconianCipher.java new file mode 100644 index 00000000000..16dfd6e674a --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/BaconianCipher.java @@ -0,0 +1,71 @@ +package com.thealgorithms.ciphers; + +import java.util.HashMap; +import java.util.Map; + +/** + * The Baconian Cipher is a substitution cipher where each letter is represented + * by a group of five binary digits (A's and B's). It can also be used to hide + * messages within other texts, making it a simple form of steganography. + * https://en.wikipedia.org/wiki/Bacon%27s_cipher + * + * @author Bennybebo + */ +public class BaconianCipher { + + private static final Map BACONIAN_MAP = new HashMap<>(); + private static final Map REVERSE_BACONIAN_MAP = new HashMap<>(); + + static { + // Initialize the Baconian cipher mappings + String[] baconianAlphabet = {"AAAAA", "AAAAB", "AAABA", "AAABB", "AABAA", "AABAB", "AABBA", "AABBB", "ABAAA", "ABAAB", "ABABA", "ABABB", "ABBAA", "ABBAB", "ABBBA", "ABBBB", "BAAAA", "BAAAB", "BAABA", "BAABB", "BABAA", "BABAB", "BABBA", "BABBB", "BBAAA", "BBAAB"}; + char letter = 'A'; + for (String code : baconianAlphabet) { + BACONIAN_MAP.put(letter, code); + REVERSE_BACONIAN_MAP.put(code, letter); + letter++; + } + + // Handle I/J as the same letter + BACONIAN_MAP.put('I', BACONIAN_MAP.get('J')); + REVERSE_BACONIAN_MAP.put(BACONIAN_MAP.get('I'), 'I'); + } + + /** + * Encrypts the given plaintext using the Baconian cipher. + * + * @param plaintext The plaintext message to encrypt. + * @return The ciphertext as a binary (A/B) sequence. + */ + public String encrypt(String plaintext) { + StringBuilder ciphertext = new StringBuilder(); + plaintext = plaintext.toUpperCase().replaceAll("[^A-Z]", ""); // Remove non-letter characters + + for (char letter : plaintext.toCharArray()) { + ciphertext.append(BACONIAN_MAP.get(letter)); + } + + return ciphertext.toString(); + } + + /** + * Decrypts the given ciphertext encoded in binary (A/B) format using the Baconian cipher. + * + * @param ciphertext The ciphertext to decrypt. + * @return The decrypted plaintext message. + */ + public String decrypt(String ciphertext) { + StringBuilder plaintext = new StringBuilder(); + + for (int i = 0; i < ciphertext.length(); i += 5) { + String code = ciphertext.substring(i, i + 5); + if (REVERSE_BACONIAN_MAP.containsKey(code)) { + plaintext.append(REVERSE_BACONIAN_MAP.get(code)); + } else { + throw new IllegalArgumentException("Invalid Baconian code: " + code); + } + } + + return plaintext.toString(); + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/BaconianCipherTest.java b/src/test/java/com/thealgorithms/ciphers/BaconianCipherTest.java new file mode 100644 index 00000000000..bb1ae5a7e4c --- /dev/null +++ b/src/test/java/com/thealgorithms/ciphers/BaconianCipherTest.java @@ -0,0 +1,34 @@ +package com.thealgorithms.ciphers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class BaconianCipherTest { + + BaconianCipher baconianCipher = new BaconianCipher(); + + @Test + void baconianCipherEncryptTest() { + // given + String plaintext = "MEET AT DAWN"; + + // when + String cipherText = baconianCipher.encrypt(plaintext); + + // then + assertEquals("ABBAAAABAAAABAABAABBAAAAABAABBAAABBAAAAABABBAABBAB", cipherText); + } + + @Test + void baconianCipherDecryptTest() { + // given + String ciphertext = "ABBAAAABAAAABAABAABBAAAAABAABBAAABBAAAAABABBAABBAB"; + + // when + String plainText = baconianCipher.decrypt(ciphertext); + + // then + assertEquals("MEETATDAWN", plainText); + } +} From 0f8cda987d57763040f45dbfc4f9b107f9b56bdb Mon Sep 17 00:00:00 2001 From: S M Jishanul Islam Date: Wed, 23 Oct 2024 00:36:14 +0600 Subject: [PATCH 37/75] Add the retrieval of minimum and maximum element from stack at O(1) (#5714) --- .../stacks/GreatestElementConstantTime.java | 74 +++++++++++++++++++ .../stacks/SmallestElementConstantTime.java | 74 +++++++++++++++++++ .../GreatestElementConstantTimeTest.java | 70 ++++++++++++++++++ .../SmallestElementConstantTimeTest.java | 69 +++++++++++++++++ 4 files changed, 287 insertions(+) create mode 100644 src/main/java/com/thealgorithms/stacks/GreatestElementConstantTime.java create mode 100644 src/main/java/com/thealgorithms/stacks/SmallestElementConstantTime.java create mode 100644 src/test/java/com/thealgorithms/stacks/GreatestElementConstantTimeTest.java create mode 100644 src/test/java/com/thealgorithms/stacks/SmallestElementConstantTimeTest.java diff --git a/src/main/java/com/thealgorithms/stacks/GreatestElementConstantTime.java b/src/main/java/com/thealgorithms/stacks/GreatestElementConstantTime.java new file mode 100644 index 00000000000..be9d0099d38 --- /dev/null +++ b/src/main/java/com/thealgorithms/stacks/GreatestElementConstantTime.java @@ -0,0 +1,74 @@ +package com.thealgorithms.stacks; + +import java.util.NoSuchElementException; +import java.util.Stack; + +/** + * A class that implements a stack that gives the maximum element in O(1) time. + * The mainStack is used to store the all the elements of the stack + * While the maxStack stores the maximum elements + * When we want to get a maximum element, we call the top of the maximum stack + * + * Problem: https://www.baeldung.com/cs/stack-constant-time + */ +public class GreatestElementConstantTime { + private Stack mainStack; // initialize a mainStack + private Stack maxStack; // initialize a maxStack + + /** + * Constructs two empty stacks + */ + public GreatestElementConstantTime() { + mainStack = new Stack<>(); + maxStack = new Stack<>(); + } + + /** + * Pushes an element onto the top of the stack. + * Checks if the element is the maximum or not + * If so, then pushes to the maximum stack + * @param data The element to be pushed onto the stack. + */ + public void push(int data) { + if (mainStack.isEmpty()) { + mainStack.push(data); + maxStack.push(data); + return; + } + + mainStack.push(data); + if (data > maxStack.peek()) { + maxStack.push(data); + } + } + + /** + * Pops an element from the stack. + * Checks if the element to be popped is the maximum or not + * If so, then pop from the minStack + * + * @throws NoSuchElementException if the stack is empty. + */ + public void pop() { + if (mainStack.isEmpty()) { + throw new NoSuchElementException("Stack is empty"); + } + + int ele = mainStack.pop(); + if (ele == maxStack.peek()) { + maxStack.pop(); + } + } + + /** + * Returns the maximum element present in the stack + * + * @return The element at the top of the maxStack, or null if the stack is empty. + */ + public Integer getMaximumElement() { + if (maxStack.isEmpty()) { + return null; + } + return maxStack.peek(); + } +} diff --git a/src/main/java/com/thealgorithms/stacks/SmallestElementConstantTime.java b/src/main/java/com/thealgorithms/stacks/SmallestElementConstantTime.java new file mode 100644 index 00000000000..9864ef9b0f9 --- /dev/null +++ b/src/main/java/com/thealgorithms/stacks/SmallestElementConstantTime.java @@ -0,0 +1,74 @@ +package com.thealgorithms.stacks; + +import java.util.NoSuchElementException; +import java.util.Stack; + +/** + * A class that implements a stack that gives the minimum element in O(1) time. + * The mainStack is used to store the all the elements of the stack + * While the minStack stores the minimum elements + * When we want to get a minimum element, we call the top of the minimum stack + * + * Problem: https://www.baeldung.com/cs/stack-constant-time + */ +public class SmallestElementConstantTime { + private Stack mainStack; // initialize a mainStack + private Stack minStack; // initialize a minStack + + /** + * Constructs two empty stacks + */ + public SmallestElementConstantTime() { + mainStack = new Stack<>(); + minStack = new Stack<>(); + } + + /** + * Pushes an element onto the top of the stack. + * Checks if the element is the minimum or not + * If so, then pushes to the minimum stack + * @param data The element to be pushed onto the stack. + */ + public void push(int data) { + if (mainStack.isEmpty()) { + mainStack.push(data); + minStack.push(data); + return; + } + + mainStack.push(data); + if (data < minStack.peek()) { + minStack.push(data); + } + } + + /** + * Pops an element from the stack. + * Checks if the element to be popped is the minimum or not + * If so, then pop from the minStack + * + * @throws NoSuchElementException if the stack is empty. + */ + public void pop() { + if (mainStack.isEmpty()) { + throw new NoSuchElementException("Stack is empty"); + } + + int ele = mainStack.pop(); + if (ele == minStack.peek()) { + minStack.pop(); + } + } + + /** + * Returns the minimum element present in the stack + * + * @return The element at the top of the minStack, or null if the stack is empty. + */ + public Integer getMinimumElement() { + if (minStack.isEmpty()) { + return null; + } + return minStack.peek(); + } +} diff --git a/src/test/java/com/thealgorithms/stacks/GreatestElementConstantTimeTest.java b/src/test/java/com/thealgorithms/stacks/GreatestElementConstantTimeTest.java new file mode 100644 index 00000000000..080592dc68e --- /dev/null +++ b/src/test/java/com/thealgorithms/stacks/GreatestElementConstantTimeTest.java @@ -0,0 +1,70 @@ +package com.thealgorithms.stacks; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.NoSuchElementException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class GreatestElementConstantTimeTest { + + private GreatestElementConstantTime constantTime; + + @BeforeEach + public void setConstantTime() { + constantTime = new GreatestElementConstantTime(); + } + + @Test + public void testMaxAtFirst() { + constantTime.push(1); + constantTime.push(10); + constantTime.push(20); + constantTime.push(5); + assertEquals(20, constantTime.getMaximumElement()); + } + + @Test + public void testMinTwo() { + constantTime.push(5); + constantTime.push(10); + constantTime.push(20); + constantTime.push(1); + assertEquals(20, constantTime.getMaximumElement()); + constantTime.pop(); + constantTime.pop(); + assertEquals(10, constantTime.getMaximumElement()); + } + + @Test + public void testNullMax() { + constantTime.push(10); + constantTime.push(20); + constantTime.pop(); + constantTime.pop(); + assertNull(constantTime.getMaximumElement()); + } + + @Test + public void testBlankHandle() { + constantTime.push(10); + constantTime.push(1); + constantTime.pop(); + constantTime.pop(); + assertThrows(NoSuchElementException.class, () -> constantTime.pop()); + } + + @Test + public void testPushPopAfterEmpty() { + constantTime.push(10); + constantTime.push(1); + constantTime.pop(); + constantTime.pop(); + constantTime.push(5); + assertEquals(5, constantTime.getMaximumElement()); + constantTime.push(1); + assertEquals(5, constantTime.getMaximumElement()); + } +} diff --git a/src/test/java/com/thealgorithms/stacks/SmallestElementConstantTimeTest.java b/src/test/java/com/thealgorithms/stacks/SmallestElementConstantTimeTest.java new file mode 100644 index 00000000000..b5eda9e8cb4 --- /dev/null +++ b/src/test/java/com/thealgorithms/stacks/SmallestElementConstantTimeTest.java @@ -0,0 +1,69 @@ +package com.thealgorithms.stacks; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.NoSuchElementException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SmallestElementConstantTimeTest { + + private SmallestElementConstantTime sect; + + @BeforeEach + public void setSect() { + sect = new SmallestElementConstantTime(); + } + + @Test + public void testMinAtFirst() { + sect.push(1); + sect.push(10); + sect.push(20); + sect.push(5); + assertEquals(1, sect.getMinimumElement()); + } + + @Test + public void testMinTwo() { + sect.push(5); + sect.push(10); + sect.push(20); + sect.push(1); + assertEquals(1, sect.getMinimumElement()); + sect.pop(); + assertEquals(5, sect.getMinimumElement()); + } + + @Test + public void testNullMin() { + sect.push(10); + sect.push(20); + sect.pop(); + sect.pop(); + assertNull(sect.getMinimumElement()); + } + + @Test + public void testBlankHandle() { + sect.push(10); + sect.push(1); + sect.pop(); + sect.pop(); + assertThrows(NoSuchElementException.class, () -> sect.pop()); + } + + @Test + public void testPushPopAfterEmpty() { + sect.push(10); + sect.push(1); + sect.pop(); + sect.pop(); + sect.push(5); + assertEquals(5, sect.getMinimumElement()); + sect.push(1); + assertEquals(1, sect.getMinimumElement()); + } +} From 60060250caec79ee1e5f9ffa93449cfccc520b30 Mon Sep 17 00:00:00 2001 From: S M Jishanul Islam Date: Wed, 23 Oct 2024 01:31:29 +0600 Subject: [PATCH 38/75] Add palindrome checker using stack (#5887) --- .../stacks/PalindromeWithStack.java | 57 ++++++++++++++ .../stacks/PalindromeWithStackTest.java | 77 +++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java create mode 100644 src/test/java/com/thealgorithms/stacks/PalindromeWithStackTest.java diff --git a/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java b/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java new file mode 100644 index 00000000000..98c439341a2 --- /dev/null +++ b/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java @@ -0,0 +1,57 @@ +package com.thealgorithms.stacks; + +import java.util.LinkedList; + +/** + * A class that implements a palindrome checker using a stack. + * The stack is used to store the characters of the string, + * which we will pop one-by-one to create the string in reverse. + * + * Reference: https://www.geeksforgeeks.org/check-whether-the-given-string-is-palindrome-using-stack/ + */ +public class PalindromeWithStack { + private LinkedList stack; + + /** + * Constructs an empty stack that stores characters. + */ + public PalindromeWithStack() { + stack = new LinkedList(); + } + + /** + * Check if the string is a palindrome or not. + * Convert all characters to lowercase and push them into a stack. + * At the same time, build a string + * Next, pop from the stack and build the reverse string + * Finally, compare these two strings + * + * @param string The string to check if it is palindrome or not. + */ + public boolean checkPalindrome(String string) { + // Create a StringBuilder to build the string from left to right + StringBuilder stringBuilder = new StringBuilder(string.length()); + // Convert all characters to lowercase + String lowercase = string.toLowerCase(); + + // Iterate through the string + for (int i = 0; i < lowercase.length(); ++i) { + char c = lowercase.charAt(i); + // Build the string from L->R + stringBuilder.append(c); + // Push to the stack + stack.push(c); + } + + // The stack contains the reverse order of the string + StringBuilder reverseString = new StringBuilder(stack.size()); + // Until the stack is not empty + while (!stack.isEmpty()) { + // Build the string from R->L + reverseString.append(stack.pop()); + } + + // Finally, compare the L->R string with the R->L string + return reverseString.toString().equals(stringBuilder.toString()); + } +} diff --git a/src/test/java/com/thealgorithms/stacks/PalindromeWithStackTest.java b/src/test/java/com/thealgorithms/stacks/PalindromeWithStackTest.java new file mode 100644 index 00000000000..47b21d5e9e9 --- /dev/null +++ b/src/test/java/com/thealgorithms/stacks/PalindromeWithStackTest.java @@ -0,0 +1,77 @@ +package com.thealgorithms.stacks; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class PalindromeWithStackTest { + + private PalindromeWithStack palindromeChecker; + + @BeforeEach + public void setUp() { + palindromeChecker = new PalindromeWithStack(); + } + + @Test + public void testValidOne() { + String testString = "Racecar"; + assertTrue(palindromeChecker.checkPalindrome(testString)); + } + + @Test + public void testInvalidOne() { + String testString = "James"; + assertFalse(palindromeChecker.checkPalindrome(testString)); + } + + @Test + public void testValidTwo() { + String testString = "madam"; + assertTrue(palindromeChecker.checkPalindrome(testString)); + } + + @Test + public void testInvalidTwo() { + String testString = "pantry"; + assertFalse(palindromeChecker.checkPalindrome(testString)); + } + + @Test + public void testValidThree() { + String testString = "RaDar"; + assertTrue(palindromeChecker.checkPalindrome(testString)); + } + + @Test + public void testInvalidThree() { + String testString = "Win"; + assertFalse(palindromeChecker.checkPalindrome(testString)); + } + + @Test + public void testBlankString() { + String testString = ""; + assertTrue(palindromeChecker.checkPalindrome(testString)); + } + + @Test + public void testStringWithNumbers() { + String testString = "12321"; + assertTrue(palindromeChecker.checkPalindrome(testString)); + } + + @Test + public void testStringWithNumbersTwo() { + String testString = "12325"; + assertFalse(palindromeChecker.checkPalindrome(testString)); + } + + @Test + public void testStringWithNumbersAndLetters() { + String testString = "po454op"; + assertTrue(palindromeChecker.checkPalindrome(testString)); + } +} From 2f9f75a3a70a11f3321396fd4a65c7331b50574c Mon Sep 17 00:00:00 2001 From: "Vignesh.S" <128959041+coffee-loves-code-2003@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:14:02 +0530 Subject: [PATCH 39/75] Add StronglyConnectedComponentOptimized (#5825) --- .../StronglyConnectedComponentOptimized.java | 84 +++++++++++++++++++ ...ronglyConnectedComponentOptimizedTest.java | 78 +++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java create mode 100644 src/test/java/com/thealgorithms/graph/StronglyConnectedComponentOptimizedTest.java diff --git a/src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java b/src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java new file mode 100644 index 00000000000..87d4e89d2c8 --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java @@ -0,0 +1,84 @@ +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Stack; + +/** + * Finds the strongly connected components in a directed graph. + * + * @param adjList The adjacency list representation of the graph. + * @param n The number of nodes in the graph. + * @return The number of strongly connected components. + */ +public class StronglyConnectedComponentOptimized { + + public void btrack(HashMap> adjList, int[] visited, Stack dfsCallsNodes, int currentNode) { + visited[currentNode] = 1; + List neighbors = adjList.get(currentNode); + // Check for null before iterating + if (neighbors != null) { + for (int neighbor : neighbors) { + if (visited[neighbor] == -1) { + btrack(adjList, visited, dfsCallsNodes, neighbor); + } + } + } + dfsCallsNodes.add(currentNode); + } + + public void btrack2(HashMap> adjRevList, int[] visited, int currentNode, List newScc) { + visited[currentNode] = 1; + newScc.add(currentNode); + List neighbors = adjRevList.get(currentNode); + // Check for null before iterating + if (neighbors != null) { + for (int neighbor : neighbors) { + if (visited[neighbor] == -1) { + btrack2(adjRevList, visited, neighbor, newScc); + } + } + } + } + + public int getOutput(HashMap> adjList, int n) { + int[] visited = new int[n]; + Arrays.fill(visited, -1); + Stack dfsCallsNodes = new Stack<>(); + + for (int i = 0; i < n; i++) { + if (visited[i] == -1) { + btrack(adjList, visited, dfsCallsNodes, i); + } + } + + HashMap> adjRevList = new HashMap<>(); + for (int i = 0; i < n; i++) { + adjRevList.put(i, new ArrayList<>()); + } + + for (int i = 0; i < n; i++) { + List neighbors = adjList.get(i); + // Check for null before iterating + if (neighbors != null) { + for (int neighbor : neighbors) { + adjRevList.get(neighbor).add(i); + } + } + } + + Arrays.fill(visited, -1); + int stronglyConnectedComponents = 0; + + while (!dfsCallsNodes.isEmpty()) { + int node = dfsCallsNodes.pop(); + if (visited[node] == -1) { + List newScc = new ArrayList<>(); + btrack2(adjRevList, visited, node, newScc); + stronglyConnectedComponents++; + } + } + + return stronglyConnectedComponents; + } +} diff --git a/src/test/java/com/thealgorithms/graph/StronglyConnectedComponentOptimizedTest.java b/src/test/java/com/thealgorithms/graph/StronglyConnectedComponentOptimizedTest.java new file mode 100644 index 00000000000..6f1c8a9d53b --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/StronglyConnectedComponentOptimizedTest.java @@ -0,0 +1,78 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class StronglyConnectedComponentOptimizedTest { + + private StronglyConnectedComponentOptimized sccOptimized; + + @BeforeEach + public void setUp() { + sccOptimized = new StronglyConnectedComponentOptimized(); + } + + @Test + public void testSingleComponent() { + // Create a simple graph with 3 nodes, all forming one SCC + HashMap> adjList = new HashMap<>(); + adjList.put(0, new ArrayList<>(List.of(1))); + adjList.put(1, new ArrayList<>(List.of(2))); + adjList.put(2, new ArrayList<>(List.of(0))); + + int result = sccOptimized.getOutput(adjList, 3); + + // The entire graph is one strongly connected component + assertEquals(1, result, "There should be 1 strongly connected component."); + } + + @Test + public void testTwoComponents() { + // Create a graph with 4 nodes and two SCCs: {0, 1, 2} and {3} + HashMap> adjList = new HashMap<>(); + adjList.put(0, new ArrayList<>(List.of(1))); + adjList.put(1, new ArrayList<>(List.of(2))); + adjList.put(2, new ArrayList<>(List.of(0))); + adjList.put(3, new ArrayList<>()); + + int result = sccOptimized.getOutput(adjList, 4); + + // There are 2 SCCs: {0, 1, 2} and {3} + assertEquals(2, result, "There should be 2 strongly connected components."); + } + + @Test + public void testDisconnectedGraph() { + // Create a graph with 4 nodes that are all disconnected + HashMap> adjList = new HashMap<>(); + adjList.put(0, new ArrayList<>()); + adjList.put(1, new ArrayList<>()); + adjList.put(2, new ArrayList<>()); + adjList.put(3, new ArrayList<>()); + + int result = sccOptimized.getOutput(adjList, 4); + + // Each node is its own strongly connected component + assertEquals(4, result, "There should be 4 strongly connected components."); + } + + @Test + public void testComplexGraph() { + // Create a more complex graph with multiple SCCs + HashMap> adjList = new HashMap<>(); + adjList.put(0, new ArrayList<>(List.of(1))); + adjList.put(1, new ArrayList<>(List.of(2))); + adjList.put(2, new ArrayList<>(List.of(0, 3))); + adjList.put(3, new ArrayList<>(List.of(4))); + adjList.put(4, new ArrayList<>(List.of(5))); + adjList.put(5, new ArrayList<>(List.of(3))); + + int result = sccOptimized.getOutput(adjList, 6); + + // There are 2 SCCs: {0, 1, 2} and {3, 4, 5} + assertEquals(2, result, "There should be 2 strongly connected components."); + } +} From aaaf96b05f3aa87a9279d1870cc05ac04e4add86 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:50:58 +0530 Subject: [PATCH 40/75] Enhance docs, add more tests in `IntegerToEnglish` (#5924) --- DIRECTORY.md | 20 ++++++ .../conversions/IntegerToEnglish.java | 72 +++++++++++++------ .../conversions/IntegerToEnglishTest.java | 31 ++++++++ 3 files changed, 101 insertions(+), 22 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 3d5b2bf61d6..20c48ce8ca4 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -64,12 +64,15 @@ * [AffineCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/AffineCipher.java) * [AtbashCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/AtbashCipher.java) * [Autokey](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Autokey.java) + * [BaconianCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/BaconianCipher.java) * [Blowfish](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Blowfish.java) * [Caesar](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Caesar.java) * [ColumnarTranspositionCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java) * [DES](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/DES.java) + * [DiffieHellman](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/DiffieHellman.java) * [ECC](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/ECC.java) * [HillCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/HillCipher.java) + * [MonoAlphabetic](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/MonoAlphabetic.java) * [PlayfairCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/PlayfairCipher.java) * [Polybius](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Polybius.java) * [ProductCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/ProductCipher.java) @@ -311,6 +314,8 @@ * [ConvexHull](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/ConvexHull.java) * [GrahamScan](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/GrahamScan.java) * [Point](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/Point.java) + * graph + * [StronglyConnectedComponentOptimized](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java) * greedyalgorithms * [ActivitySelection](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/ActivitySelection.java) * [BinaryAddition](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/BinaryAddition.java) @@ -564,6 +569,7 @@ * [UnionFind](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/UnionFind.java) * [UpperBound](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/UpperBound.java) * sorts + * [AdaptiveMergeSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/AdaptiveMergeSort.java) * [BeadSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/BeadSort.java) * [BinaryInsertionSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/BinaryInsertionSort.java) * [BitonicSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/BitonicSort.java) @@ -603,6 +609,7 @@ * [SortUtils](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SortUtils.java) * [SortUtilsRandomGenerator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SortUtilsRandomGenerator.java) * [SpreadSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SpreadSort.java) + * [StalinSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/StalinSort.java) * [StoogeSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/StoogeSort.java) * [StrandSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/StrandSort.java) * [SwapSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SwapSort.java) @@ -616,6 +623,7 @@ * [CelebrityFinder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/CelebrityFinder.java) * [DecimalToAnyUsingStack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/DecimalToAnyUsingStack.java) * [DuplicateBrackets](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/DuplicateBrackets.java) + * [GreatestElementConstantTime](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/GreatestElementConstantTime.java) * [InfixToPostfix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/InfixToPostfix.java) * [InfixToPrefix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/InfixToPrefix.java) * [LargestRectangle](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/LargestRectangle.java) @@ -624,10 +632,12 @@ * [MinStackUsingTwoStacks](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/MinStackUsingTwoStacks.java) * [NextGreaterElement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/NextGreaterElement.java) * [NextSmallerElement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/NextSmallerElement.java) + * [PalindromeWithStack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java) * [PostfixEvaluator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/PostfixEvaluator.java) * [PostfixToInfix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/PostfixToInfix.java) * [PrefixEvaluator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/PrefixEvaluator.java) * [PrefixToInfix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/PrefixToInfix.java) + * [SmallestElementConstantTime](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/SmallestElementConstantTime.java) * [SortStack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/SortStack.java) * [StackPostfixNotation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/StackPostfixNotation.java) * [StackUsingTwoQueues](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/StackUsingTwoQueues.java) @@ -726,12 +736,15 @@ * [AffineCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/AffineCipherTest.java) * [AtbashTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/AtbashTest.java) * [AutokeyTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/AutokeyTest.java) + * [BaconianCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/BaconianCipherTest.java) * [BlowfishTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/BlowfishTest.java) * [CaesarTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/CaesarTest.java) * [ColumnarTranspositionCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/ColumnarTranspositionCipherTest.java) * [DESTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/DESTest.java) + * [DiffieHellmanTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/DiffieHellmanTest.java) * [ECCTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/ECCTest.java) * [HillCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/HillCipherTest.java) + * [MonoAlphabeticTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/MonoAlphabeticTest.java) * [PlayfairTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/PlayfairTest.java) * [PolybiusTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/PolybiusTest.java) * [RailFenceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/RailFenceTest.java) @@ -914,6 +927,8 @@ * [BresenhamLineTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/BresenhamLineTest.java) * [ConvexHullTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/ConvexHullTest.java) * [GrahamScanTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/GrahamScanTest.java) + * graph + * [StronglyConnectedComponentOptimizedTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/graph/StronglyConnectedComponentOptimizedTest.java) * greedyalgorithms * [ActivitySelectionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/ActivitySelectionTest.java) * [BinaryAdditionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/BinaryAdditionTest.java) @@ -1133,6 +1148,7 @@ * [UnionFindTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/UnionFindTest.java) * [UpperBoundTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/UpperBoundTest.java) * sorts + * [AdaptiveMergeSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/AdaptiveMergeSortTest.java) * [BeadSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/BeadSortTest.java) * [BinaryInsertionSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/BinaryInsertionSortTest.java) * [BitonicSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/BitonicSortTest.java) @@ -1171,6 +1187,7 @@ * [SortUtilsRandomGeneratorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SortUtilsRandomGeneratorTest.java) * [SortUtilsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SortUtilsTest.java) * [SpreadSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SpreadSortTest.java) + * [StalinSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/StalinSortTest.java) * [StoogeSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/StoogeSortTest.java) * [StrandSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/StrandSortTest.java) * [SwapSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SwapSortTest.java) @@ -1184,6 +1201,7 @@ * [CelebrityFinderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/CelebrityFinderTest.java) * [DecimalToAnyUsingStackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/DecimalToAnyUsingStackTest.java) * [DuplicateBracketsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/DuplicateBracketsTest.java) + * [GreatestElementConstantTimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/GreatestElementConstantTimeTest.java) * [InfixToPostfixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/InfixToPostfixTest.java) * [InfixToPrefixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/InfixToPrefixTest.java) * [LargestRectangleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/LargestRectangleTest.java) @@ -1191,10 +1209,12 @@ * [MinStackUsingTwoStacksTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/MinStackUsingTwoStacksTest.java) * [NextGreaterElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/NextGreaterElementTest.java) * [NextSmallerElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/NextSmallerElementTest.java) + * [PalindromeWithStackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/PalindromeWithStackTest.java) * [PostfixEvaluatorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/PostfixEvaluatorTest.java) * [PostfixToInfixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/PostfixToInfixTest.java) * [PrefixEvaluatorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/PrefixEvaluatorTest.java) * [PrefixToInfixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/PrefixToInfixTest.java) + * [SmallestElementConstantTimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/SmallestElementConstantTimeTest.java) * [SortStackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/SortStackTest.java) * [StackPostfixNotationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/StackPostfixNotationTest.java) * [StackUsingTwoQueuesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/StackUsingTwoQueuesTest.java) diff --git a/src/main/java/com/thealgorithms/conversions/IntegerToEnglish.java b/src/main/java/com/thealgorithms/conversions/IntegerToEnglish.java index d3b938bf492..e85c608af5d 100644 --- a/src/main/java/com/thealgorithms/conversions/IntegerToEnglish.java +++ b/src/main/java/com/thealgorithms/conversions/IntegerToEnglish.java @@ -2,7 +2,28 @@ import java.util.Map; +/** + * A utility class to convert integers to their English word representation. + * + *

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

+ * + *

Example Usage

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

This class uses two maps:

+ *
    + *
  • BASE_NUMBERS_MAP: Holds English words for numbers 0-20, multiples of 10 up to 90, and 100.
  • + *
  • THOUSAND_POWER_MAP: Maps powers of 1000 (e.g., Thousand, Million, Billion).
  • + *
+ */ public final class IntegerToEnglish { + private static final Map BASE_NUMBERS_MAP = Map.ofEntries(Map.entry(0, ""), Map.entry(1, "One"), Map.entry(2, "Two"), Map.entry(3, "Three"), Map.entry(4, "Four"), Map.entry(5, "Five"), Map.entry(6, "Six"), Map.entry(7, "Seven"), Map.entry(8, "Eight"), Map.entry(9, "Nine"), Map.entry(10, "Ten"), Map.entry(11, "Eleven"), Map.entry(12, "Twelve"), Map.entry(13, "Thirteen"), Map.entry(14, "Fourteen"), Map.entry(15, "Fifteen"), Map.entry(16, "Sixteen"), Map.entry(17, "Seventeen"), Map.entry(18, "Eighteen"), Map.entry(19, "Nineteen"), Map.entry(20, "Twenty"), Map.entry(30, "Thirty"), Map.entry(40, "Forty"), Map.entry(50, "Fifty"), Map.entry(60, "Sixty"), Map.entry(70, "Seventy"), Map.entry(80, "Eighty"), Map.entry(90, "Ninety"), Map.entry(100, "Hundred")); @@ -13,35 +34,46 @@ private IntegerToEnglish() { } /** - converts numbers < 1000 to english words + * Converts numbers less than 1000 into English words. + * + * @param number the integer value (0-999) to convert + * @return the English word representation of the input number */ private static String convertToWords(int number) { int remainder = number % 100; - - String result; + StringBuilder result = new StringBuilder(); if (remainder <= 20) { - result = BASE_NUMBERS_MAP.get(remainder); + result.append(BASE_NUMBERS_MAP.get(remainder)); } else if (BASE_NUMBERS_MAP.containsKey(remainder)) { - result = BASE_NUMBERS_MAP.get(remainder); + result.append(BASE_NUMBERS_MAP.get(remainder)); } else { int tensDigit = remainder / 10; int onesDigit = remainder % 10; - - result = String.format("%s %s", BASE_NUMBERS_MAP.get(tensDigit * 10), BASE_NUMBERS_MAP.get(onesDigit)); + String tens = BASE_NUMBERS_MAP.getOrDefault(tensDigit * 10, ""); + String ones = BASE_NUMBERS_MAP.getOrDefault(onesDigit, ""); + result.append(tens); + if (ones != null && !ones.isEmpty()) { + result.append(" ").append(ones); + } } int hundredsDigit = number / 100; - if (hundredsDigit > 0) { - result = String.format("%s %s%s", BASE_NUMBERS_MAP.get(hundredsDigit), BASE_NUMBERS_MAP.get(100), result.isEmpty() ? "" : " " + result); + if (result.length() > 0) { + result.insert(0, " "); + } + result.insert(0, String.format("%s Hundred", BASE_NUMBERS_MAP.get(hundredsDigit))); } - return result; + return result.toString().trim(); } /** - Only convert groups of three digit if they are non-zero + * Converts a non-negative integer to its English word representation. + * + * @param number the integer to convert (0-2,147,483,647) + * @return the English word representation of the input number */ public static String integerToEnglishWords(int number) { if (number == 0) { @@ -49,7 +81,6 @@ public static String integerToEnglishWords(int number) { } StringBuilder result = new StringBuilder(); - int index = 0; while (number > 0) { @@ -58,23 +89,20 @@ public static String integerToEnglishWords(int number) { if (remainder > 0) { String subResult = convertToWords(remainder); - if (!subResult.isEmpty()) { - if (!result.isEmpty()) { - result.insert(0, subResult + " " + THOUSAND_POWER_MAP.get(index) + " "); - } else { - if (index > 0) { - result = new StringBuilder(subResult + " " + THOUSAND_POWER_MAP.get(index)); - } else { - result = new StringBuilder(subResult); - } + if (index > 0) { + subResult += " " + THOUSAND_POWER_MAP.get(index); + } + if (result.length() > 0) { + result.insert(0, " "); } + result.insert(0, subResult); } } index++; } - return result.toString(); + return result.toString().trim(); } } diff --git a/src/test/java/com/thealgorithms/conversions/IntegerToEnglishTest.java b/src/test/java/com/thealgorithms/conversions/IntegerToEnglishTest.java index 49c43402aec..c2a94794b7f 100644 --- a/src/test/java/com/thealgorithms/conversions/IntegerToEnglishTest.java +++ b/src/test/java/com/thealgorithms/conversions/IntegerToEnglishTest.java @@ -11,5 +11,36 @@ public void testIntegerToEnglish() { assertEquals("Two Billion One Hundred Forty Seven Million Four Hundred Eighty Three Thousand Six Hundred Forty Seven", IntegerToEnglish.integerToEnglishWords(2147483647)); assertEquals("One Million Two Hundred Thirty Four Thousand Five Hundred Sixty Seven", IntegerToEnglish.integerToEnglishWords(1234567)); assertEquals("Twelve Thousand Three Hundred Forty Five", IntegerToEnglish.integerToEnglishWords(12345)); + assertEquals("One Hundred", IntegerToEnglish.integerToEnglishWords(100)); + assertEquals("Zero", IntegerToEnglish.integerToEnglishWords(0)); + } + + @Test + public void testSmallNumbers() { + assertEquals("Ten", IntegerToEnglish.integerToEnglishWords(10)); + assertEquals("Nineteen", IntegerToEnglish.integerToEnglishWords(19)); + assertEquals("Twenty One", IntegerToEnglish.integerToEnglishWords(21)); + assertEquals("Ninety Nine", IntegerToEnglish.integerToEnglishWords(99)); + } + + @Test + public void testHundreds() { + assertEquals("One Hundred One", IntegerToEnglish.integerToEnglishWords(101)); + assertEquals("Five Hundred Fifty", IntegerToEnglish.integerToEnglishWords(550)); + assertEquals("Nine Hundred Ninety Nine", IntegerToEnglish.integerToEnglishWords(999)); + } + + @Test + public void testThousands() { + assertEquals("One Thousand", IntegerToEnglish.integerToEnglishWords(1000)); + assertEquals("Ten Thousand One", IntegerToEnglish.integerToEnglishWords(10001)); + assertEquals("Seventy Six Thousand Five Hundred Forty Three", IntegerToEnglish.integerToEnglishWords(76543)); + } + + @Test + public void testEdgeCases() { + assertEquals("One Million", IntegerToEnglish.integerToEnglishWords(1_000_000)); + assertEquals("One Billion", IntegerToEnglish.integerToEnglishWords(1_000_000_000)); + assertEquals("Two Thousand", IntegerToEnglish.integerToEnglishWords(2000)); } } From 7bbdae5fe063624484cf4e9819c52a7e0b392555 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:55:20 +0530 Subject: [PATCH 41/75] Enhance docs, add more tests in `RomanToInteger` (#5926) --- .../conversions/RomanToInteger.java | 85 ++++++++++++------- .../conversions/RomanToIntegerTest.java | 19 ++++- 2 files changed, 73 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/thealgorithms/conversions/RomanToInteger.java b/src/main/java/com/thealgorithms/conversions/RomanToInteger.java index 1934e9b264c..a634c720326 100644 --- a/src/main/java/com/thealgorithms/conversions/RomanToInteger.java +++ b/src/main/java/com/thealgorithms/conversions/RomanToInteger.java @@ -3,9 +3,27 @@ import java.util.HashMap; import java.util.Map; +/** + * A utility class to convert Roman numerals into integers. + * + *

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

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

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

+ *   MCMXCIV = 1000 + (1000 - 100) + (100 - 10) + (5 - 1) = 1994
+ * 
+ */ public final class RomanToInteger { - private RomanToInteger() { - } private static final Map ROMAN_TO_INT = new HashMap<>() { { @@ -19,44 +37,53 @@ private RomanToInteger() { } }; + private RomanToInteger() { + } + + /** + * Converts a single Roman numeral character to its integer value. + * + * @param symbol the Roman numeral character + * @return the corresponding integer value + * @throws IllegalArgumentException if the symbol is not a valid Roman numeral + */ private static int romanSymbolToInt(final char symbol) { return ROMAN_TO_INT.computeIfAbsent(symbol, c -> { throw new IllegalArgumentException("Unknown Roman symbol: " + c); }); } - // Roman Number = Roman Numerals - /** - * This function convert Roman number into Integer + * Converts a Roman numeral string to its integer equivalent. + * Steps: + *
    + *
  1. Iterate over the string from right to left.
  2. + *
  3. For each character, convert it to an integer value.
  4. + *
  5. If the current value is greater than or equal to the max previous value, add it.
  6. + *
  7. Otherwise, subtract it from the sum.
  8. + *
  9. Update the max previous value.
  10. + *
  11. Return the sum.
  12. + *
* - * @param a Roman number string - * @return integer + * @param roman the Roman numeral string + * @return the integer value of the Roman numeral + * @throws IllegalArgumentException if the input contains invalid Roman characters + * @throws NullPointerException if the input is {@code null} */ - public static int romanToInt(String a) { - a = a.toUpperCase(); - char prev = ' '; + public static int romanToInt(String roman) { + if (roman == null) { + throw new NullPointerException("Input cannot be null"); + } + roman = roman.toUpperCase(); int sum = 0; - - int newPrev = 0; - for (int i = a.length() - 1; i >= 0; i--) { - char c = a.charAt(i); - - if (prev != ' ') { - // checking current Number greater than previous or not - newPrev = romanSymbolToInt(prev) > newPrev ? romanSymbolToInt(prev) : newPrev; - } - - int currentNum = romanSymbolToInt(c); - - // if current number greater than prev max previous then add - if (currentNum >= newPrev) { - sum += currentNum; + int maxPrevValue = 0; + for (int i = roman.length() - 1; i >= 0; i--) { + int currentValue = romanSymbolToInt(roman.charAt(i)); + if (currentValue >= maxPrevValue) { + sum += currentValue; + maxPrevValue = currentValue; } else { - // subtract upcoming number until upcoming number not greater than prev max - sum -= currentNum; + sum -= currentValue; } - - prev = c; } return sum; diff --git a/src/test/java/com/thealgorithms/conversions/RomanToIntegerTest.java b/src/test/java/com/thealgorithms/conversions/RomanToIntegerTest.java index f03563971cb..971eff8c74f 100644 --- a/src/test/java/com/thealgorithms/conversions/RomanToIntegerTest.java +++ b/src/test/java/com/thealgorithms/conversions/RomanToIntegerTest.java @@ -8,16 +8,31 @@ public class RomanToIntegerTest { @Test - public void testRomanToInteger() { + public void testValidRomanToInteger() { assertEquals(1994, RomanToInteger.romanToInt("MCMXCIV")); assertEquals(58, RomanToInteger.romanToInt("LVIII")); assertEquals(1804, RomanToInteger.romanToInt("MDCCCIV")); + assertEquals(9, RomanToInteger.romanToInt("IX")); + assertEquals(4, RomanToInteger.romanToInt("IV")); + assertEquals(3000, RomanToInteger.romanToInt("MMM")); } @Test - void testRomanToIntegerThrows() { + public void testLowercaseInput() { + assertEquals(1994, RomanToInteger.romanToInt("mcmxciv")); + assertEquals(58, RomanToInteger.romanToInt("lviii")); + } + + @Test + public void testInvalidRomanNumerals() { assertThrows(IllegalArgumentException.class, () -> RomanToInteger.romanToInt("Z")); assertThrows(IllegalArgumentException.class, () -> RomanToInteger.romanToInt("MZI")); assertThrows(IllegalArgumentException.class, () -> RomanToInteger.romanToInt("MMMO")); } + + @Test + public void testEmptyAndNullInput() { + assertEquals(0, RomanToInteger.romanToInt("")); // Empty string case + assertThrows(NullPointerException.class, () -> RomanToInteger.romanToInt(null)); // Null input case + } } From d85f192421bbbc0728aaec476b84974942588a10 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:03:00 +0530 Subject: [PATCH 42/75] Enhance docs, add more tests in `OctalToBinary` (#5942) --- .../conversions/OctalToBinary.java | 45 +++++++++++++++++-- .../conversions/OctalToBinaryTest.java | 20 +++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/thealgorithms/conversions/OctalToBinary.java b/src/main/java/com/thealgorithms/conversions/OctalToBinary.java index 6b01c2f65cf..a66db97633b 100644 --- a/src/main/java/com/thealgorithms/conversions/OctalToBinary.java +++ b/src/main/java/com/thealgorithms/conversions/OctalToBinary.java @@ -1,14 +1,40 @@ package com.thealgorithms.conversions; /** - * Converts any Octal Number to a Binary Number + * A utility class to convert an octal (base-8) number into its binary (base-2) representation. + * + *

This class provides methods to: + *

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

Octal to Binary Conversion:

+ *

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

+ * + *

Example Usage

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

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

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

For example: + *

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

+ * + * @param octalDigit a single octal digit (0-7) + * @return the binary equivalent as a long + */ public static long convertOctalDigitToBinary(int octalDigit) { long binaryDigit = 0; int binaryMultiplier = 1; diff --git a/src/test/java/com/thealgorithms/conversions/OctalToBinaryTest.java b/src/test/java/com/thealgorithms/conversions/OctalToBinaryTest.java index 86cf692c525..8e007151c30 100644 --- a/src/test/java/com/thealgorithms/conversions/OctalToBinaryTest.java +++ b/src/test/java/com/thealgorithms/conversions/OctalToBinaryTest.java @@ -12,4 +12,24 @@ public void testConvertOctalToBinary() { assertEquals(101010, OctalToBinary.convertOctalToBinary(52)); assertEquals(110, OctalToBinary.convertOctalToBinary(6)); } + + @Test + public void testConvertOctalToBinarySingleDigit() { + assertEquals(0, OctalToBinary.convertOctalToBinary(0)); + assertEquals(1, OctalToBinary.convertOctalToBinary(1)); + assertEquals(111, OctalToBinary.convertOctalToBinary(7)); + } + + @Test + public void testConvertOctalToBinaryMultipleDigits() { + assertEquals(100110111, OctalToBinary.convertOctalToBinary(467)); + assertEquals(111101, OctalToBinary.convertOctalToBinary(75)); + assertEquals(111100101, OctalToBinary.convertOctalToBinary(745)); + } + + @Test + public void testConvertOctalToBinaryWithZeroPadding() { + assertEquals(100001010, OctalToBinary.convertOctalToBinary(412)); + assertEquals(101101110, OctalToBinary.convertOctalToBinary(556)); + } } From 757d10c277b8148cd44ada53dc49c9ab37fd5d79 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:06:43 +0530 Subject: [PATCH 43/75] Remove 'main', add tests in `TurkishToLatinConversion` (#5944) --- DIRECTORY.md | 1 + .../conversions/TurkishToLatinConversion.java | 19 +++--------- .../TurkishToLatinConversionTest.java | 31 +++++++++++++++++++ 3 files changed, 36 insertions(+), 15 deletions(-) create mode 100644 src/test/java/com/thealgorithms/conversions/TurkishToLatinConversionTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 20c48ce8ca4..4f427686092 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -775,6 +775,7 @@ * [OctalToHexadecimalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/OctalToHexadecimalTest.java) * [PhoneticAlphabetConverterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/PhoneticAlphabetConverterTest.java) * [RomanToIntegerTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/RomanToIntegerTest.java) + * [TurkishToLatinConversionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/TurkishToLatinConversionTest.java) * [UnitConversionsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/UnitConversionsTest.java) * [UnitsConverterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/UnitsConverterTest.java) * datastructures diff --git a/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java b/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java index 4d13b8b7fd5..30030de6c1b 100644 --- a/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java +++ b/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java @@ -1,7 +1,5 @@ package com.thealgorithms.conversions; -import java.util.Scanner; - /** * Converts turkish character to latin character * @@ -11,21 +9,12 @@ public final class TurkishToLatinConversion { private TurkishToLatinConversion() { } - /** - * Main method - * - * @param args Command line arguments - */ - public static void main(String[] args) { - Scanner sc = new Scanner(System.in); - System.out.println("Input the string: "); - String b = sc.next(); - System.out.println("Converted: " + convertTurkishToLatin(b)); - sc.close(); - } - /** * This method converts a turkish character to latin character. + * Steps: + * 1. Define turkish characters and their corresponding latin characters + * 2. Replace all turkish characters with their corresponding latin characters + * 3. Return the converted string * * @param param String paramter * @return String diff --git a/src/test/java/com/thealgorithms/conversions/TurkishToLatinConversionTest.java b/src/test/java/com/thealgorithms/conversions/TurkishToLatinConversionTest.java new file mode 100644 index 00000000000..87e40c78e6a --- /dev/null +++ b/src/test/java/com/thealgorithms/conversions/TurkishToLatinConversionTest.java @@ -0,0 +1,31 @@ +package com.thealgorithms.conversions; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +public class TurkishToLatinConversionTest { + + @ParameterizedTest + @CsvSource({ + "'çalışma', 'calisma'", // Turkish to Latin conversion for lowercase + "'ÇALIŞMA', 'CALISMA'", // Turkish to Latin conversion for uppercase + "'İSTANBUL', 'ISTANBUL'", // Special case of 'İ' to 'I' + "'istanbul', 'istanbul'", // Special case of 'ı' to 'i' + "'GÜL', 'GUL'", // Special case of 'Ü' to 'U' + "'gül', 'gul'", // Special case of 'ü' to 'u' + "'ÖĞRENME', 'OGRENME'", // Special case of 'Ö' to 'O' and 'Ğ' to 'G' + "'öğrenme', 'ogrenme'", // Special case of 'ö' to 'o' and 'ğ' to 'g' + "'ŞEHIR', 'SEHIR'", // Special case of 'Ş' to 'S' + "'şehir', 'sehir'", // Special case of 'ş' to 's' + "'HELLO', 'HELLO'", // String with no Turkish characters, should remain unchanged + "'Merhaba Dünya!', 'Merhaba Dunya!'", // Mixed Turkish and Latin characters with punctuation + "'Çift kişilik yataklı odalar', 'Cift kisilik yatakli odalar'", // Full sentence conversion + "'', ''" // Empty string case + }) + public void + testConvertTurkishToLatin(String input, String expectedOutput) { + assertEquals(expectedOutput, TurkishToLatinConversion.convertTurkishToLatin(input)); + } +} From 03fe106e328aecc629e8c8174fff02f126d7a8e9 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:11:48 +0530 Subject: [PATCH 44/75] Enhance docs, add more tests in `PhoneticAlphabetConverter` (#5943) --- .../PhoneticAlphabetConverter.java | 16 ++++++++++ .../PhoneticAlphabetConverterTest.java | 30 +++++++++++-------- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/thealgorithms/conversions/PhoneticAlphabetConverter.java b/src/main/java/com/thealgorithms/conversions/PhoneticAlphabetConverter.java index bcea1862e99..730ce2214e2 100644 --- a/src/main/java/com/thealgorithms/conversions/PhoneticAlphabetConverter.java +++ b/src/main/java/com/thealgorithms/conversions/PhoneticAlphabetConverter.java @@ -58,9 +58,25 @@ private PhoneticAlphabetConverter() { PHONETIC_MAP.put('9', "Nine"); } + /** + * Converts text to the NATO phonetic alphabet. + * Steps: + * 1. Convert the text to uppercase. + * 2. Iterate over each character in the text. + * 3. Get the phonetic equivalent of the character from the map. + * 4. Append the phonetic equivalent to the result. + * 5. Append a space to separate the phonetic equivalents. + * 6. Return the result. + * + * @param text the text to convert + * @return the NATO phonetic alphabet + */ public static String textToPhonetic(String text) { StringBuilder phonetic = new StringBuilder(); for (char c : text.toUpperCase().toCharArray()) { + if (Character.isWhitespace(c)) { + continue; + } phonetic.append(PHONETIC_MAP.getOrDefault(c, String.valueOf(c))).append(" "); } return phonetic.toString().trim(); diff --git a/src/test/java/com/thealgorithms/conversions/PhoneticAlphabetConverterTest.java b/src/test/java/com/thealgorithms/conversions/PhoneticAlphabetConverterTest.java index 07847302c8d..360af7fa0f5 100644 --- a/src/test/java/com/thealgorithms/conversions/PhoneticAlphabetConverterTest.java +++ b/src/test/java/com/thealgorithms/conversions/PhoneticAlphabetConverterTest.java @@ -2,20 +2,26 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; public class PhoneticAlphabetConverterTest { - @Test - public void testTextToPhonetic() { - assertEquals("Alpha Bravo", PhoneticAlphabetConverter.textToPhonetic("AB")); - assertEquals("Alpha Bravo Charlie", PhoneticAlphabetConverter.textToPhonetic("ABC")); - assertEquals("Alpha One Bravo Two Charlie Three", PhoneticAlphabetConverter.textToPhonetic("A1B2C3")); - assertEquals("Hotel Echo Lima Lima Oscar", PhoneticAlphabetConverter.textToPhonetic("Hello")); - assertEquals("One Two Three", PhoneticAlphabetConverter.textToPhonetic("123")); - assertEquals("Alpha Bravo Charlie Delta Echo Foxtrot Golf Hotel India Juliett Kilo Lima Mike November Oscar Papa Quebec Romeo Sierra Tango Uniform Victor Whiskey X-ray Yankee Zulu Zero One Two Three Four Five Six Seven Eight Nine", - PhoneticAlphabetConverter.textToPhonetic("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")); - assertEquals("Alpha Bravo Charlie Delta Echo Foxtrot Golf Hotel India Juliett Kilo Lima Mike November Oscar Papa Quebec Romeo Sierra Tango Uniform Victor Whiskey X-ray Yankee Zulu Zero One Two Three Four Five Six Seven Eight Nine", - PhoneticAlphabetConverter.textToPhonetic("abcdefghijklmnopqrstuvwxyz0123456789")); + @ParameterizedTest + @CsvSource({ + "'AB', 'Alpha Bravo'", "'ABC', 'Alpha Bravo Charlie'", "'A1B2C3', 'Alpha One Bravo Two Charlie Three'", "'Hello', 'Hotel Echo Lima Lima Oscar'", "'123', 'One Two Three'", + "'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 'Alpha Bravo Charlie Delta Echo Foxtrot Golf Hotel India Juliett Kilo Lima Mike November Oscar Papa Quebec Romeo Sierra Tango Uniform Victor Whiskey X-ray Yankee Zulu Zero One Two Three Four Five Six Seven Eight Nine'", + "'abcdefghijklmnopqrstuvwxyz0123456789', 'Alpha Bravo Charlie Delta Echo Foxtrot Golf Hotel India Juliett Kilo Lima Mike November Oscar Papa Quebec Romeo Sierra Tango Uniform Victor Whiskey X-ray Yankee Zulu Zero One Two Three Four Five Six Seven Eight Nine'", + "'', ''", // Empty string case + "'A B C', 'Alpha Bravo Charlie'", // String with spaces + "'A@B#C', 'Alpha @ Bravo # Charlie'", // Special characters + "'A B C 123', 'Alpha Bravo Charlie One Two Three'", // Mixed letters, digits, and spaces + "'a b c', 'Alpha Bravo Charlie'", // Lowercase letters with spaces + "'123!@#', 'One Two Three ! @ #'", // Numbers with special characters + "'HELLO WORLD', 'Hotel Echo Lima Lima Oscar Whiskey Oscar Romeo Lima Delta'" // Words with space + }) + public void + testTextToPhonetic(String input, String expectedOutput) { + assertEquals(expectedOutput, PhoneticAlphabetConverter.textToPhonetic(input)); } } From a8a1abac643f763f058ce430f4c93946583f635b Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:17:59 +0530 Subject: [PATCH 45/75] Enhance docs in `UnitConversions` (#5945) --- .../conversions/UnitConversions.java | 37 +++++++++++++++++++ .../conversions/UnitConversionsTest.java | 4 +- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/thealgorithms/conversions/UnitConversions.java b/src/main/java/com/thealgorithms/conversions/UnitConversions.java index abc06a0f886..15f74a21a17 100644 --- a/src/main/java/com/thealgorithms/conversions/UnitConversions.java +++ b/src/main/java/com/thealgorithms/conversions/UnitConversions.java @@ -5,10 +5,47 @@ import java.util.Map; import org.apache.commons.lang3.tuple.Pair; +/** + * A utility class to perform unit conversions between different measurement systems. + * + *

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

Example Usage

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

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

Temperature Scales Supported

+ *
    + *
  • Celsius
  • + *
  • Fahrenheit
  • + *
  • Kelvin
  • + *
  • Réaumur
  • + *
  • Delisle
  • + *
  • Rankine
  • + *
+ */ public final class UnitConversions { private UnitConversions() { } + /** + * A preconfigured instance of {@link UnitsConverter} for temperature conversions. + * The converter handles conversions between the following temperature units: + *
    + *
  • Kelvin to Celsius
  • + *
  • Celsius to Fahrenheit
  • + *
  • Réaumur to Celsius
  • + *
  • Delisle to Celsius
  • + *
  • Rankine to Kelvin
  • + *
+ */ public static final UnitsConverter TEMPERATURE = new UnitsConverter(Map.ofEntries(entry(Pair.of("Kelvin", "Celsius"), new AffineConverter(1.0, -273.15)), entry(Pair.of("Celsius", "Fahrenheit"), new AffineConverter(9.0 / 5.0, 32.0)), entry(Pair.of("Réaumur", "Celsius"), new AffineConverter(5.0 / 4.0, 0.0)), entry(Pair.of("Delisle", "Celsius"), new AffineConverter(-2.0 / 3.0, 100.0)), entry(Pair.of("Rankine", "Kelvin"), new AffineConverter(5.0 / 9.0, 0.0)))); } diff --git a/src/test/java/com/thealgorithms/conversions/UnitConversionsTest.java b/src/test/java/com/thealgorithms/conversions/UnitConversionsTest.java index 073e7d6de2c..3c4e3d5e4c5 100644 --- a/src/test/java/com/thealgorithms/conversions/UnitConversionsTest.java +++ b/src/test/java/com/thealgorithms/conversions/UnitConversionsTest.java @@ -13,8 +13,8 @@ public class UnitConversionsTest { private static void addData(Stream.Builder builder, Map values) { - for (final var first : values.entrySet()) { - for (final var second : values.entrySet()) { + for (var first : values.entrySet()) { + for (var second : values.entrySet()) { if (!first.getKey().equals(second.getKey())) { builder.add(Arguments.of(first.getKey(), second.getKey(), first.getValue(), second.getValue())); } From fef1f3ca44247f62ec1530cd8d2b2d44872fc757 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:25:23 +0530 Subject: [PATCH 46/75] Enhance docs, add more tests in `UnitsConverter` (#5946) --- .../conversions/UnitsConverter.java | 60 +++++++++++++++++++ .../conversions/UnitsConverterTest.java | 17 ++++++ 2 files changed, 77 insertions(+) diff --git a/src/main/java/com/thealgorithms/conversions/UnitsConverter.java b/src/main/java/com/thealgorithms/conversions/UnitsConverter.java index 81c4d456207..00690b2c0f9 100644 --- a/src/main/java/com/thealgorithms/conversions/UnitsConverter.java +++ b/src/main/java/com/thealgorithms/conversions/UnitsConverter.java @@ -7,6 +7,43 @@ import java.util.Set; import org.apache.commons.lang3.tuple.Pair; +/** + * A class that handles unit conversions using affine transformations. + * + *

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

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

Key features include: + *

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

Example Usage

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

Exception Handling

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

Accepts a map of basic conversions and automatically generates inverse and + * transitive conversions. + * + * @param basicConversions the initial set of unit conversions to add. + */ public UnitsConverter(final Map, AffineConverter> basicConversions) { conversions = computeAllConversions(basicConversions); units = extractUnits(conversions); } + /** + * Converts a value from one unit to another. + * + * @param inputUnit the unit of the input value. + * @param outputUnit the unit to convert the value into. + * @param value the value to convert. + * @return the converted value in the target unit. + * @throws IllegalArgumentException if inputUnit equals outputUnit. + * @throws NoSuchElementException if no conversion exists between the units. + */ public double convert(final String inputUnit, final String outputUnit, final double value) { if (inputUnit.equals(outputUnit)) { throw new IllegalArgumentException("inputUnit must be different from outputUnit."); @@ -81,6 +136,11 @@ public double convert(final String inputUnit, final String outputUnit, final dou return conversions.computeIfAbsent(conversionKey, k -> { throw new NoSuchElementException("No converter for: " + k); }).convert(value); } + /** + * Retrieves the set of all units supported by this converter. + * + * @return a set of available units. + */ public Set availableUnits() { return units; } diff --git a/src/test/java/com/thealgorithms/conversions/UnitsConverterTest.java b/src/test/java/com/thealgorithms/conversions/UnitsConverterTest.java index 580a66bc01e..0952129efb4 100644 --- a/src/test/java/com/thealgorithms/conversions/UnitsConverterTest.java +++ b/src/test/java/com/thealgorithms/conversions/UnitsConverterTest.java @@ -1,10 +1,12 @@ package com.thealgorithms.conversions; import static java.util.Map.entry; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Set; import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.Test; @@ -24,4 +26,19 @@ void testConvertThrowsForUnknownUnits() { assertThrows(NoSuchElementException.class, () -> someConverter.convert("X", "A", 20.0)); assertThrows(NoSuchElementException.class, () -> someConverter.convert("X", "Y", 20.0)); } + + @Test + void testAvailableUnits() { + final UnitsConverter someConverter = new UnitsConverter(Map.ofEntries(entry(Pair.of("Celsius", "Fahrenheit"), new AffineConverter(9.0 / 5.0, 32.0)), entry(Pair.of("Kelvin", "Celsius"), new AffineConverter(1.0, -273.15)))); + assertEquals(Set.of("Celsius", "Fahrenheit", "Kelvin"), someConverter.availableUnits()); + } + + @Test + void testInvertConversion() { + final UnitsConverter someConverter = new UnitsConverter(Map.ofEntries(entry(Pair.of("A", "B"), new AffineConverter(2.0, 5.0)))); + // Check conversion from A -> B + assertEquals(25.0, someConverter.convert("A", "B", 10.0), 0.0001); + // Check inverse conversion from B -> A + assertEquals(10.0, someConverter.convert("B", "A", 25.0), 0.0001); + } } From 730eb5a5f699f70dd56d2e61b047f560b62e24e1 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:31:26 +0530 Subject: [PATCH 47/75] Enhance docs, add more tests in `Bag` (#5947) --- .../datastructures/bags/Bag.java | 46 ++++++++++++--- .../datastructures/bag/BagTest.java | 56 +++++++++++++++++-- 2 files changed, 89 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/bags/Bag.java b/src/main/java/com/thealgorithms/datastructures/bags/Bag.java index 1bb143fabda..afc3bbe40cc 100644 --- a/src/main/java/com/thealgorithms/datastructures/bags/Bag.java +++ b/src/main/java/com/thealgorithms/datastructures/bags/Bag.java @@ -4,14 +4,18 @@ import java.util.NoSuchElementException; /** - * A collection that allows adding and iterating over elements but does not support element removal. + * A generic collection that allows adding and iterating over elements but does not support + * element removal. This class implements a simple bag data structure, which can hold duplicate + * elements and provides operations to check for membership and the size of the collection. + * + *

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

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

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

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

The iterator provides a way to traverse the elements in the order they were added. + * * @return an iterator that iterates over the elements in the bag */ @Override @@ -88,19 +100,35 @@ private static class ListIterator implements Iterator { private Node currentElement; + /** + * Constructs a ListIterator starting from the given first element. + * + * @param firstElement the first element of the bag to iterate over + */ ListIterator(Node firstElement) { this.currentElement = firstElement; } + /** + * Checks if there are more elements to iterate over. + * + * @return {@code true} if there are more elements; {@code false} otherwise + */ @Override public boolean hasNext() { return currentElement != null; } + /** + * Returns the next element in the iteration. + * + * @return the next element in the bag + * @throws NoSuchElementException if there are no more elements to return + */ @Override public E next() { if (!hasNext()) { - throw new NoSuchElementException(); + throw new NoSuchElementException("No more elements in the bag."); } E element = currentElement.content; currentElement = currentElement.nextElement; diff --git a/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java b/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java index c0fe107bfba..b7e64851383 100644 --- a/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java +++ b/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import com.thealgorithms.datastructures.bags.Bag; @@ -68,12 +69,12 @@ void testContainsMethod() { } @Test - void testContainsAfterRemoveOperation() { + void testContainsAfterAdditions() { Bag bag = new Bag<>(); bag.add("item1"); bag.add("item2"); - assertTrue(bag.contains("item1"), "Bag should contain 'item1' before removal"); - assertTrue(bag.contains("item2"), "Bag should contain 'item2' before removal"); + assertTrue(bag.contains("item1"), "Bag should contain 'item1' after addition"); + assertTrue(bag.contains("item2"), "Bag should contain 'item2' after addition"); } @Test @@ -106,6 +107,53 @@ void testRemoveMethodThrowsException() { Bag bag = new Bag<>(); bag.add("item1"); Iterator iterator = bag.iterator(); - org.junit.jupiter.api.Assertions.assertThrows(UnsupportedOperationException.class, iterator::remove, "Remove operation should throw UnsupportedOperationException"); + assertThrows(UnsupportedOperationException.class, iterator::remove, "Remove operation should throw UnsupportedOperationException"); + } + + @Test + void testMultipleDuplicates() { + Bag bag = new Bag<>(); + bag.add("item1"); + bag.add("item1"); + bag.add("item1"); // Add three duplicates + + assertEquals(3, bag.size(), "Bag size should be 3 after adding three duplicates"); + assertTrue(bag.contains("item1"), "Bag should contain 'item1'"); + } + + @Test + void testLargeNumberOfElements() { + Bag bag = new Bag<>(); + for (int i = 0; i < 1000; i++) { + bag.add(i); + } + assertEquals(1000, bag.size(), "Bag should contain 1000 elements"); + } + + @Test + void testMixedTypeElements() { + Bag bag = new Bag<>(); + bag.add("string"); + bag.add(1); + bag.add(2.0); + + assertTrue(bag.contains("string"), "Bag should contain a string"); + assertTrue(bag.contains(1), "Bag should contain an integer"); + assertTrue(bag.contains(2.0), "Bag should contain a double"); + } + + @Test + void testIteratorWithDuplicates() { + Bag bag = new Bag<>(); + bag.add("item1"); + bag.add("item1"); + bag.add("item2"); + + int count = 0; + for (String item : bag) { + assertTrue(item.equals("item1") || item.equals("item2"), "Item should be either 'item1' or 'item2'"); + count++; + } + assertEquals(3, count, "Iterator should traverse all 3 items including duplicates"); } } From 8db9d107481e2ff799b7636f08d30818c9db281a Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:37:14 +0530 Subject: [PATCH 48/75] Enhance docs, add more tests in `BloomFilter` (#5948) --- .../bloomfilter/BloomFilter.java | 43 +++++++++++++--- .../bloomfilter/BloomFilterTest.java | 51 +++++++++++++++++++ 2 files changed, 87 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java b/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java index 33ea22c3d27..a2edd3db2d8 100644 --- a/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java +++ b/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java @@ -4,6 +4,11 @@ /** * A generic BloomFilter implementation for probabilistic membership checking. + *

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

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

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

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

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

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

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

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

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

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

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

* * @param word the string to compute - * @return the sum of ASCII values of the characters + * @return the sum of ASCII values of the characters in the string */ private int asciiString(String word) { int sum = 0; diff --git a/src/test/java/com/thealgorithms/datastructures/bloomfilter/BloomFilterTest.java b/src/test/java/com/thealgorithms/datastructures/bloomfilter/BloomFilterTest.java index b19801a5ad7..048eb7e481a 100644 --- a/src/test/java/com/thealgorithms/datastructures/bloomfilter/BloomFilterTest.java +++ b/src/test/java/com/thealgorithms/datastructures/bloomfilter/BloomFilterTest.java @@ -62,4 +62,55 @@ void testMultipleInsertions() { Assertions.assertFalse(bloomFilter.contains("key" + 200)); } + + @Test + void testEmptyFilterContains() { + Assertions.assertFalse(bloomFilter.contains("notInserted"), "Filter should not contain any elements when empty"); + Assertions.assertFalse(bloomFilter.contains(null), "Filter should not contain null elements"); + } + + @Test + void testDifferentTypes() { + BloomFilter filter = new BloomFilter<>(3, 100); + filter.insert("string"); + filter.insert(123); + filter.insert(45.67); + + Assertions.assertTrue(filter.contains("string"), "Filter should contain the string 'string'"); + Assertions.assertTrue(filter.contains(123), "Filter should contain the integer 123"); + Assertions.assertTrue(filter.contains(45.67), "Filter should contain the double 45.67"); + Assertions.assertFalse(filter.contains("missing"), "Filter should not contain elements that were not inserted"); + } + + @Test + void testFalsePositiveAfterInsertions() { + bloomFilter.insert("cat"); + bloomFilter.insert("dog"); + bloomFilter.insert("fish"); + + // Checking for an element that was not added + Assertions.assertFalse(bloomFilter.contains("bird"), "Filter should not contain 'bird' which was never inserted"); + + // To increase chances of false positives, we can add more items + for (int i = 0; i < 100; i++) { + bloomFilter.insert("item" + i); + } + + Assertions.assertFalse(bloomFilter.contains("nonexistent"), "Filter should not contain 'nonexistent' which was never inserted"); + } + + @Test + void testBoundaryConditions() { + BloomFilter filter = new BloomFilter<>(3, 10); + filter.insert("a"); + filter.insert("b"); + filter.insert("c"); + filter.insert("d"); + + Assertions.assertTrue(filter.contains("a"), "Filter should contain 'a'"); + Assertions.assertTrue(filter.contains("b"), "Filter should contain 'b'"); + Assertions.assertTrue(filter.contains("c"), "Filter should contain 'c'"); + Assertions.assertTrue(filter.contains("d"), "Filter should contain 'd'"); + Assertions.assertFalse(filter.contains("e"), "Filter should not contain 'e' which was not inserted"); + } } From d868982a72f8945b0d3519ddaeeab606082304da Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:05:26 +0530 Subject: [PATCH 49/75] Enhance docs, add more tests in `MRUCache` (#5951) --- .../datastructures/caches/MRUCache.java | 97 +++++++++++++++---- .../datastructures/caches/MRUCacheTest.java | 81 ++++++++++++++-- 2 files changed, 153 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java b/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java index 9c155be8b19..93b13e6ad65 100644 --- a/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java +++ b/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java @@ -4,14 +4,17 @@ import java.util.Map; /** - * Most recently used (MRU) + * Represents a Most Recently Used (MRU) Cache. *

- * In contrast to Least Recently Used (LRU), MRU discards the most recently used - * items first. - * https://en.wikipedia.org/wiki/Cache_replacement_policies#Most_recently_used_(MRU) + * In contrast to the Least Recently Used (LRU) strategy, the MRU caching policy + * evicts the most recently accessed items first. This class provides methods to + * store key-value pairs and manage cache eviction based on this policy. * - * @param key type - * @param value type + * For more information, refer to: + * MRU on Wikipedia. + * + * @param the type of keys maintained by this cache + * @param the type of values associated with the keys */ public class MRUCache { @@ -21,40 +24,74 @@ public class MRUCache { private int cap; private static final int DEFAULT_CAP = 100; + /** + * Creates an MRUCache with the default capacity. + */ public MRUCache() { setCapacity(DEFAULT_CAP); } + /** + * Creates an MRUCache with a specified capacity. + * + * @param cap the maximum number of items the cache can hold + */ + public MRUCache(int cap) { + setCapacity(cap); + } + + /** + * Sets the capacity of the cache and evicts items if the new capacity + * is less than the current number of items. + * + * @param newCapacity the new capacity to set + */ private void setCapacity(int newCapacity) { checkCapacity(newCapacity); - for (int i = data.size(); i > newCapacity; i--) { + while (data.size() > newCapacity) { Entry evicted = evict(); data.remove(evicted.getKey()); } this.cap = newCapacity; } + /** + * Checks if the specified capacity is valid. + * + * @param capacity the capacity to check + * @throws IllegalArgumentException if the capacity is less than or equal to zero + */ private void checkCapacity(int capacity) { if (capacity <= 0) { - throw new RuntimeException("capacity must greater than 0!"); + throw new IllegalArgumentException("Capacity must be greater than 0!"); } } + /** + * Evicts the most recently used entry from the cache. + * + * @return the evicted entry + * @throws RuntimeException if the cache is empty + */ private Entry evict() { if (head == null) { - throw new RuntimeException("cache cannot be empty!"); + throw new RuntimeException("Cache cannot be empty!"); } final Entry evicted = this.tail; tail = evicted.getPreEntry(); - tail.setNextEntry(null); + if (tail != null) { + tail.setNextEntry(null); + } evicted.setNextEntry(null); return evicted; } - public MRUCache(int cap) { - setCapacity(cap); - } - + /** + * Retrieves the value associated with the specified key. + * + * @param key the key whose associated value is to be returned + * @return the value associated with the specified key, or null if the key does not exist + */ public V get(K key) { if (!data.containsKey(key)) { return null; @@ -64,11 +101,19 @@ public V get(K key) { return entry.getValue(); } + /** + * Associates the specified value with the specified key in the cache. + * If the key already exists, its value is updated and the entry is moved to the most recently used position. + * If the cache is full, the most recently used entry is evicted before adding the new entry. + * + * @param key the key with which the specified value is to be associated + * @param value the value to be associated with the specified key + */ public void put(K key, V value) { if (data.containsKey(key)) { - final Entry exitingEntry = data.get(key); - exitingEntry.setValue(value); - moveEntryToLast(exitingEntry); + final Entry existingEntry = data.get(key); + existingEntry.setValue(value); + moveEntryToLast(existingEntry); return; } Entry newEntry; @@ -84,6 +129,11 @@ public void put(K key, V value) { data.put(key, newEntry); } + /** + * Adds a new entry to the cache and updates the head and tail pointers accordingly. + * + * @param newEntry the new entry to be added + */ private void addNewEntry(Entry newEntry) { if (data.isEmpty()) { head = newEntry; @@ -96,6 +146,11 @@ private void addNewEntry(Entry newEntry) { tail = newEntry; } + /** + * Moves the specified entry to the most recently used position in the cache. + * + * @param entry the entry to be moved + */ private void moveEntryToLast(Entry entry) { if (tail == entry) { return; @@ -117,8 +172,14 @@ private void moveEntryToLast(Entry entry) { tail = entry; } + /** + * A nested class representing an entry in the cache, which holds a key-value pair + * and references to the previous and next entries in the linked list structure. + * + * @param the type of the key + * @param the type of the value + */ static final class Entry { - private Entry preEntry; private Entry nextEntry; private I key; diff --git a/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java b/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java index 447feb38e78..50303ba239f 100644 --- a/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java +++ b/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java @@ -11,27 +11,27 @@ public class MRUCacheTest { @Test public void putAndGetIntegerValues() { - MRUCache lruCache = new MRUCache<>(SIZE); + MRUCache mruCache = new MRUCache<>(SIZE); for (int i = 0; i < SIZE; i++) { - lruCache.put(i, i); + mruCache.put(i, i); } for (int i = 0; i < SIZE; i++) { - assertEquals(i, lruCache.get(i)); + assertEquals(i, mruCache.get(i)); } } @Test public void putAndGetStringValues() { - MRUCache lruCache = new MRUCache<>(SIZE); + MRUCache mruCache = new MRUCache<>(SIZE); for (int i = 0; i < SIZE; i++) { - lruCache.put("key" + i, "value" + i); + mruCache.put("key" + i, "value" + i); } for (int i = 0; i < SIZE; i++) { - assertEquals("value" + i, lruCache.get("key" + i)); + assertEquals("value" + i, mruCache.get("key" + i)); } } @@ -53,6 +53,73 @@ public void overCapacity() { mruCache.put(i, i); } - assertEquals(9, mruCache.get(9)); + // After inserting 10 items, the cache should have evicted the least recently used ones. + assertEquals(9, mruCache.get(9)); // Most recently used + assertEquals(0, mruCache.get(0)); // Least recently used, should be evicted + } + + @Test + public void overwriteExistingKey() { + MRUCache mruCache = new MRUCache<>(SIZE); + mruCache.put(1, "one"); + mruCache.put(1, "uno"); // Overwriting the value for key 1 + + assertEquals("uno", mruCache.get(1)); + assertNull(mruCache.get(2)); // Ensure other keys are unaffected + } + + @Test + public void evictionOrder() { + MRUCache mruCache = new MRUCache<>(SIZE); + + for (int i = 0; i < SIZE; i++) { + mruCache.put(i, i); + } + + // Access a key to make it most recently used + mruCache.get(2); + + // Add new items to trigger eviction + mruCache.put(5, 5); + mruCache.put(6, 6); + + // Key 3 should be evicted since 2 is the most recently used + assertEquals(3, mruCache.get(3)); + assertEquals(4, mruCache.get(4)); // Key 4 should still be available + assertEquals(6, mruCache.get(6)); // Key 6 should be available + } + + @Test + public void cacheHandlesLargeValues() { + MRUCache mruCache = new MRUCache<>(SIZE); + + for (int i = 0; i < SIZE; i++) { + mruCache.put("key" + i, "value" + i); + } + + // Verify values + for (int i = 0; i < SIZE; i++) { + assertEquals("value" + i, mruCache.get("key" + i)); + } + + // Add large value + mruCache.put("largeKey", "largeValue"); + + // Verify eviction of the least recently used (key 0 should be evicted) + assertEquals("value0", mruCache.get("key0")); + assertEquals("largeValue", mruCache.get("largeKey")); + } + + @Test + public void testEmptyCacheBehavior() { + MRUCache mruCache = new MRUCache<>(SIZE); + + // Verify that accessing any key returns null + assertNull(mruCache.get(1)); + assertNull(mruCache.get(100)); + + // Adding to cache and checking again + mruCache.put(1, 10); + assertEquals(10, mruCache.get(1)); } } From 4f7957ff140d8857e3c3d987bf14056b63286739 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 23 Oct 2024 23:30:38 +0530 Subject: [PATCH 50/75] Enhance docs, add more tests in `DynamicArray` (#5952) --- .../dynamicarray/DynamicArray.java | 107 ++++++++++++++---- .../dynamicarray/DynamicArrayTest.java | 3 + 2 files changed, 88 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java b/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java index a5fa9cbe94e..cd5dc580b69 100644 --- a/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java +++ b/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java @@ -10,21 +10,24 @@ import java.util.stream.StreamSupport; /** - * This class implements a dynamic array. + * This class implements a dynamic array, which can grow or shrink in size + * as elements are added or removed. It provides an array-like interface + * with methods to add, remove, and access elements, along with iterators + * to traverse the elements. * - * @param the type that each index of the array will hold + * @param the type of elements that this array can hold */ public class DynamicArray implements Iterable { private static final int DEFAULT_CAPACITY = 16; private int size; - private int modCount; // Tracks structural modifications for the iterator + private int modCount; // Tracks structural modifications for iterator integrity private Object[] elements; /** - * Constructor with initial capacity. + * Constructs a new DynamicArray with the specified initial capacity. * - * @param capacity the starting length of the desired array + * @param capacity the initial capacity of the array * @throws IllegalArgumentException if the specified capacity is negative */ public DynamicArray(final int capacity) { @@ -37,14 +40,15 @@ public DynamicArray(final int capacity) { } /** - * No-args constructor with default capacity. + * Constructs a new DynamicArray with a default initial capacity. */ public DynamicArray() { this(DEFAULT_CAPACITY); } /** - * Adds an element to the array. If full, creates a new array with double the size. + * Adds an element to the end of the array. If the array is full, it + * creates a new array with double the size to accommodate the new element. * * @param element the element to be added to the array */ @@ -55,11 +59,11 @@ public void add(final E element) { } /** - * Places an element at the desired index, expanding capacity if necessary. + * Places an element at the specified index, expanding capacity if necessary. * - * @param index the index for the element to be placed - * @param element the element to be inserted - * @throws IndexOutOfBoundsException if n is less than 0 or greater or equal to the number of elements in the array + * @param index the index at which the element is to be placed + * @param element the element to be inserted at the specified index + * @throws IndexOutOfBoundsException if index is less than 0 or greater than or equal to the number of elements */ public void put(final int index, E element) { if (index < 0) { @@ -74,11 +78,11 @@ public void put(final int index, E element) { } /** - * Gets the element at a given index. + * Retrieves the element at the specified index. * - * @param index the desired index of the element + * @param index the index of the element to retrieve * @return the element at the specified index - * @throws IndexOutOfBoundsException if n is less than 0 or greater or equal to the number of elements in the array + * @throws IndexOutOfBoundsException if index is less than 0 or greater than or equal to the current size */ @SuppressWarnings("unchecked") public E get(final int index) { @@ -89,11 +93,11 @@ public E get(final int index) { } /** - * Removes an element from the array. + * Removes and returns the element at the specified index. * * @param index the index of the element to be removed - * @return the element removed - * @throws IndexOutOfBoundsException if n is less than 0 or greater or equal to the number of elements in the array + * @return the element that was removed from the array + * @throws IndexOutOfBoundsException if index is less than 0 or greater than or equal to the current size */ public E remove(final int index) { if (index < 0 || index >= size) { @@ -106,16 +110,16 @@ public E remove(final int index) { } /** - * Gets the size of the array. + * Returns the current number of elements in the array. * - * @return the size + * @return the number of elements in the array */ public int getSize() { return size; } /** - * Checks if the array is empty. + * Checks whether the array is empty. * * @return true if the array contains no elements, false otherwise */ @@ -123,10 +127,20 @@ public boolean isEmpty() { return size == 0; } + /** + * Returns a sequential stream with this collection as its source. + * + * @return a stream of the elements in the array + */ public Stream stream() { return StreamSupport.stream(spliterator(), false); } + /** + * Ensures that the array has enough capacity to hold the specified number of elements. + * + * @param minCapacity the minimum capacity required + */ private void ensureCapacity(int minCapacity) { if (minCapacity > elements.length) { int newCapacity = Math.max(elements.length * 2, minCapacity); @@ -134,6 +148,12 @@ private void ensureCapacity(int minCapacity) { } } + /** + * Removes the element at the specified index without resizing the array. + * This method shifts any subsequent elements to the left and clears the last element. + * + * @param index the index of the element to remove + */ private void fastRemove(int index) { int numMoved = size - index - 1; if (numMoved > 0) { @@ -142,31 +162,58 @@ private void fastRemove(int index) { elements[--size] = null; // Clear to let GC do its work } + /** + * Returns a string representation of the array, including only the elements that are currently stored. + * + * @return a string containing the elements in the array + */ @Override public String toString() { return Arrays.toString(Arrays.copyOf(elements, size)); } + /** + * Returns an iterator over the elements in this array in proper sequence. + * + * @return an Iterator over the elements in the array + */ @Override public Iterator iterator() { return new DynamicArrayIterator(); } + /** + * Private iterator class for the DynamicArray. + */ private final class DynamicArrayIterator implements Iterator { private int cursor; private int expectedModCount; + /** + * Constructs a new iterator for the dynamic array. + */ DynamicArrayIterator() { this.expectedModCount = modCount; } + /** + * Checks if there are more elements in the iteration. + * + * @return true if there are more elements, false otherwise + */ @Override public boolean hasNext() { checkForComodification(); return cursor < size; } + /** + * Returns the next element in the iteration. + * + * @return the next element in the iteration + * @throws NoSuchElementException if the iteration has no more elements + */ @Override @SuppressWarnings("unchecked") public E next() { @@ -177,22 +224,38 @@ public E next() { return (E) elements[cursor++]; } + /** + * Removes the last element returned by this iterator. + * + * @throws IllegalStateException if the next method has not yet been called, or the remove method has already been called after the last call to the next method + */ @Override public void remove() { if (cursor <= 0) { - throw new IllegalStateException(); + throw new IllegalStateException("Cannot remove element before calling next()"); } checkForComodification(); DynamicArray.this.remove(--cursor); - expectedModCount = ++modCount; + expectedModCount = modCount; } + /** + * Checks for concurrent modifications to the array during iteration. + * + * @throws ConcurrentModificationException if the array has been modified structurally + */ private void checkForComodification() { if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } } + /** + * Performs the given action for each remaining element in the iterator until all elements have been processed. + * + * @param action the action to be performed for each element + * @throws NullPointerException if the specified action is null + */ @Override public void forEachRemaining(Consumer action) { Objects.requireNonNull(action); diff --git a/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java b/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java index 8e067086689..8fdc93e1ca2 100644 --- a/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java +++ b/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java @@ -24,6 +24,8 @@ public void setUp() { public void testGetElement() { array.add("Alice"); array.add("Bob"); + array.add("Charlie"); + array.add("David"); assertEquals("Bob", array.get(1)); } @@ -31,6 +33,7 @@ public void testGetElement() { public void testGetInvalidIndex() { assertThrows(IndexOutOfBoundsException.class, () -> array.get(-1)); assertThrows(IndexOutOfBoundsException.class, () -> array.get(10)); + assertThrows(IndexOutOfBoundsException.class, () -> array.get(100)); } @Test From 520e46443e3a782f949b070231395129e89d6169 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 23 Oct 2024 23:37:04 +0530 Subject: [PATCH 51/75] Enhance docs, add more tests in `FordFulkerson` (#5953) --- .../datastructures/graphs/FordFulkerson.java | 18 +++ .../graphs/FordFulkersonTest.java | 122 ++++++++++++++++++ 2 files changed, 140 insertions(+) diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java b/src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java index af2665cfaeb..b3a2053d54b 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java @@ -3,12 +3,30 @@ import java.util.LinkedList; import java.util.Queue; +/** + * This class implements the Ford-Fulkerson algorithm to compute the maximum flow + * in a flow network. + * + *

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

+ */ public final class FordFulkerson { private static final int INF = Integer.MAX_VALUE; private FordFulkerson() { } + /** + * Computes the maximum flow in a flow network using the Ford-Fulkerson algorithm. + * + * @param vertexCount the number of vertices in the flow network + * @param capacity a 2D array representing the capacity of edges in the network + * @param flow a 2D array representing the current flow in the network + * @param source the source vertex in the flow network + * @param sink the sink vertex in the flow network + * @return the total maximum flow from the source to the sink + */ public static int networkFlow(int vertexCount, int[][] capacity, int[][] flow, int source, int sink) { int totalFlow = 0; diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/FordFulkersonTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/FordFulkersonTest.java index 908296aab5c..29e373e361a 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/FordFulkersonTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/FordFulkersonTest.java @@ -91,4 +91,126 @@ public void testComplexNetwork() { int maxFlow = FordFulkerson.networkFlow(vertexCount, capacity, flow, 0, 4); assertEquals(19, maxFlow); } + + @Test + public void testLargeNetwork() { + int vertexCount = 8; + int[][] capacity = new int[vertexCount][vertexCount]; + int[][] flow = new int[vertexCount][vertexCount]; + + // Setting up a large network + capacity[0][1] = 10; + capacity[0][2] = 5; + capacity[1][3] = 15; + capacity[2][3] = 10; + capacity[1][4] = 10; + capacity[3][5] = 10; + capacity[4][5] = 5; + capacity[4][6] = 10; + capacity[5][7] = 10; + capacity[6][7] = 15; + + int maxFlow = FordFulkerson.networkFlow(vertexCount, capacity, flow, 0, 7); + assertEquals(15, maxFlow); // Maximum flow should be 15 + } + + @Test + public void testMultipleSourcesAndSinks() { + int vertexCount = 7; + int[][] capacity = new int[vertexCount][vertexCount]; + int[][] flow = new int[vertexCount][vertexCount]; + + // Creating multiple sources and sinks scenario + capacity[0][1] = 10; // Source 1 + capacity[0][2] = 5; + capacity[1][3] = 15; + capacity[2][3] = 10; + capacity[3][4] = 10; // Sink 1 + capacity[3][5] = 5; + capacity[3][6] = 10; // Sink 2 + capacity[5][6] = 10; + + int maxFlow = FordFulkerson.networkFlow(vertexCount, capacity, flow, 0, 4); + assertEquals(10, maxFlow); // Maximum flow should be 10 + } + + @Test + public void testDisconnectedGraph() { + int vertexCount = 6; + int[][] capacity = new int[vertexCount][vertexCount]; + int[][] flow = new int[vertexCount][vertexCount]; + + // No connection between source and sink + capacity[0][1] = 10; // Only one edge not connected to the sink + capacity[1][2] = 10; + capacity[3][4] = 10; + + int maxFlow = FordFulkerson.networkFlow(vertexCount, capacity, flow, 0, 5); + assertEquals(0, maxFlow); // No flow should be possible + } + + @Test + public void testZeroCapacityEdge() { + int vertexCount = 4; + int[][] capacity = new int[vertexCount][vertexCount]; + int[][] flow = new int[vertexCount][vertexCount]; + + // Including a zero capacity edge + capacity[0][1] = 10; + capacity[0][2] = 0; // Zero capacity + capacity[1][3] = 5; + capacity[2][3] = 10; + + int maxFlow = FordFulkerson.networkFlow(vertexCount, capacity, flow, 0, 3); + assertEquals(5, maxFlow); // Flow only possible through 0 -> 1 -> 3 + } + + @Test + public void testAllEdgesZeroCapacity() { + int vertexCount = 5; + int[][] capacity = new int[vertexCount][vertexCount]; + int[][] flow = new int[vertexCount][vertexCount]; + + // All edges with zero capacity + capacity[0][1] = 0; + capacity[1][2] = 0; + capacity[2][3] = 0; + capacity[3][4] = 0; + + int maxFlow = FordFulkerson.networkFlow(vertexCount, capacity, flow, 0, 4); + assertEquals(0, maxFlow); // No flow should be possible + } + + @Test + public void testCycleGraph() { + int vertexCount = 4; + int[][] capacity = new int[vertexCount][vertexCount]; + int[][] flow = new int[vertexCount][vertexCount]; + + // Setting up a cycle + capacity[0][1] = 10; + capacity[1][2] = 5; + capacity[2][0] = 5; // This creates a cycle + capacity[1][3] = 15; + capacity[2][3] = 10; + + int maxFlow = FordFulkerson.networkFlow(vertexCount, capacity, flow, 0, 3); + assertEquals(10, maxFlow); // Maximum flow should be 10 + } + + @Test + public void testFlowWithExcessCapacity() { + int vertexCount = 5; + int[][] capacity = new int[vertexCount][vertexCount]; + int[][] flow = new int[vertexCount][vertexCount]; + + // Extra capacity in the flow + capacity[0][1] = 20; + capacity[1][2] = 10; + capacity[2][3] = 15; + capacity[1][3] = 5; + + int maxFlow = FordFulkerson.networkFlow(vertexCount, capacity, flow, 0, 3); + assertEquals(15, maxFlow); // Maximum flow should be 15 (20 from 0->1 and 10->2, limited by 15->3) + } } From b64e53cd3d949c3ff6626a7e92ac149e9b0074d6 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 23 Oct 2024 23:46:19 +0530 Subject: [PATCH 52/75] Enhance docs, add more tests in `LRUCache` (#5950) --- .../datastructures/caches/LRUCache.java | 80 +++++++++- .../datastructures/caches/LRUCacheTest.java | 137 +++++++++++++++--- 2 files changed, 186 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java b/src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java index 97818ff8335..ec39d2a6ed2 100644 --- a/src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java +++ b/src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java @@ -4,15 +4,40 @@ import java.util.Map; /** - * Least recently used (LRU) - *

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

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

+ * + *

Features:

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

Implementation Details:

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

Example usage:

+ *
+ * LRUCache cache = new LRUCache<>(3); // Create cache with capacity 3
+ * cache.put("A", 1); // Cache: A=1
+ * cache.put("B", 2); // Cache: A=1, B=2
+ * cache.put("C", 3); // Cache: A=1, B=2, C=3
+ * cache.get("A");    // Cache: B=2, C=3, A=1 (A moved to end)
+ * cache.put("D", 4); // Cache: C=3, A=1, D=4 (B evicted)
+ * 
+ * + * @param the type of keys maintained by this cache + * @param the type of mapped values */ public class LRUCache { @@ -30,6 +55,11 @@ public LRUCache(int cap) { setCapacity(cap); } + /** + * Returns the current capacity of the cache. + * + * @param newCapacity the new capacity of the cache + */ private void setCapacity(int newCapacity) { checkCapacity(newCapacity); for (int i = data.size(); i > newCapacity; i--) { @@ -39,6 +69,11 @@ private void setCapacity(int newCapacity) { this.cap = newCapacity; } + /** + * Evicts the least recently used item from the cache. + * + * @return the evicted entry + */ private Entry evict() { if (head == null) { throw new RuntimeException("cache cannot be empty!"); @@ -50,12 +85,25 @@ private Entry evict() { return evicted; } + /** + * Checks if the capacity is valid. + * + * @param capacity the capacity to check + */ private void checkCapacity(int capacity) { if (capacity <= 0) { throw new RuntimeException("capacity must greater than 0!"); } } + /** + * Returns the value to which the specified key is mapped, or null if this cache contains no + * mapping for the key. + * + * @param key the key whose associated value is to be returned + * @return the value to which the specified key is mapped, or null if this cache contains no + * mapping for the key + */ public V get(K key) { if (!data.containsKey(key)) { return null; @@ -65,6 +113,11 @@ public V get(K key) { return entry.getValue(); } + /** + * Moves the specified entry to the end of the list. + * + * @param entry the entry to move + */ private void moveNodeToLast(Entry entry) { if (tail == entry) { return; @@ -86,6 +139,12 @@ private void moveNodeToLast(Entry entry) { tail = entry; } + /** + * Associates the specified value with the specified key in this cache. + * + * @param key the key with which the specified value is to be associated + * @param value the value to be associated with the specified key + */ public void put(K key, V value) { if (data.containsKey(key)) { final Entry existingEntry = data.get(key); @@ -107,6 +166,11 @@ public void put(K key, V value) { data.put(key, newEntry); } + /** + * Adds a new entry to the end of the list. + * + * @param newEntry the entry to add + */ private void addNewEntry(Entry newEntry) { if (data.isEmpty()) { head = newEntry; diff --git a/src/test/java/com/thealgorithms/datastructures/caches/LRUCacheTest.java b/src/test/java/com/thealgorithms/datastructures/caches/LRUCacheTest.java index c56ada06002..99b9952435c 100644 --- a/src/test/java/com/thealgorithms/datastructures/caches/LRUCacheTest.java +++ b/src/test/java/com/thealgorithms/datastructures/caches/LRUCacheTest.java @@ -3,56 +3,147 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; public class LRUCacheTest { - private static final int SIZE = 5; + private LRUCache cache; + + @BeforeEach + void setUp() { + cache = new LRUCache<>(SIZE); + } @Test - public void putAndGetIntegerValues() { - LRUCache lruCache = new LRUCache<>(SIZE); + public void testBasicOperations() { + cache.put(1, 100); + assertEquals(100, cache.get(1)); + assertNull(cache.get(2)); + } + @Test + public void testEvictionPolicy() { + // Fill cache to capacity for (int i = 0; i < SIZE; i++) { - lruCache.put(i, i); + cache.put(i, i * 100); } + // Verify all elements are present for (int i = 0; i < SIZE; i++) { - assertEquals(i, lruCache.get(i)); + assertEquals(i * 100, cache.get(i)); } + + // Add one more element, causing eviction of least recently used + cache.put(SIZE, SIZE * 100); + + // First element should be evicted + assertNull(cache.get(0)); + assertEquals(SIZE * 100, cache.get(SIZE)); } @Test - public void putAndGetStringValues() { - LRUCache lruCache = new LRUCache<>(SIZE); - + public void testAccessOrder() { + // Fill cache for (int i = 0; i < SIZE; i++) { - lruCache.put("key" + i, "value" + i); + cache.put(i, i); } - for (int i = 0; i < SIZE; i++) { - assertEquals("value" + i, lruCache.get("key" + i)); - } + // Access first element, making it most recently used + cache.get(0); + + // Add new element, should evict second element (1) + cache.put(SIZE, SIZE); + + assertEquals(0, cache.get(0)); // Should still exist + assertNull(cache.get(1)); // Should be evicted + assertEquals(SIZE, cache.get(SIZE)); // Should exist + } + + @Test + public void testUpdateExistingKey() { + cache.put(1, 100); + assertEquals(100, cache.get(1)); + + // Update existing key + cache.put(1, 200); + assertEquals(200, cache.get(1)); } @Test - public void nullKeysAndValues() { - LRUCache mruCache = new LRUCache<>(SIZE); - mruCache.put(null, 2); - mruCache.put(6, null); + public void testNullValues() { + cache.put(1, null); + assertNull(cache.get(1)); - assertEquals(2, mruCache.get(null)); - assertNull(mruCache.get(6)); + // Update null to non-null + cache.put(1, 100); + assertEquals(100, cache.get(1)); + + // Update non-null to null + cache.put(1, null); + assertNull(cache.get(1)); + } + + @Test + public void testStringKeysAndValues() { + LRUCache stringCache = new LRUCache<>(SIZE); + + stringCache.put("key1", "value1"); + stringCache.put("key2", "value2"); + + assertEquals("value1", stringCache.get("key1")); + assertEquals("value2", stringCache.get("key2")); + } + + @Test + public void testLongSequenceOfOperations() { + // Add elements beyond capacity multiple times + for (int i = 0; i < SIZE * 3; i++) { + cache.put(i, i * 100); + + // Verify only the last SIZE elements are present + for (int j = Math.max(0, i - SIZE + 1); j <= i; j++) { + assertEquals(j * 100, cache.get(j)); + } + + // Verify elements before the window are evicted + if (i >= SIZE) { + assertNull(cache.get(i - SIZE)); + } + } } @Test - public void overCapacity() { - LRUCache mruCache = new LRUCache<>(SIZE); + void testCustomObjects() { + class TestObject { + private final String value; - for (int i = 0; i < 10; i++) { - mruCache.put(i, i); + TestObject(String value) { + this.value = value; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof TestObject) { + return value.equals(((TestObject) obj).value); + } + return false; + } + + @Override + public int hashCode() { + return value == null ? 0 : value.hashCode(); + } } - assertEquals(9, mruCache.get(9)); + LRUCache objectCache = new LRUCache<>(SIZE); + TestObject obj1 = new TestObject("test1"); + TestObject obj2 = new TestObject("test2"); + + objectCache.put(1, obj1); + objectCache.put(2, obj2); + + assertEquals(obj1, objectCache.get(1)); + assertEquals(obj2, objectCache.get(2)); } } From be0b1d58d649dba9901214d23b0110d0ca1e9b56 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 23 Oct 2024 23:51:03 +0530 Subject: [PATCH 53/75] Enhance docs, add more tests in `LFUCache` (#5949) --- .../datastructures/caches/LFUCache.java | 15 ++-- .../datastructures/caches/LFUCacheTest.java | 88 +++++++++++++++---- 2 files changed, 82 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java b/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java index 4e233224e36..f0d8ea8f7ff 100644 --- a/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java +++ b/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java @@ -6,16 +6,21 @@ /** * The {@code LFUCache} class implements a Least Frequently Used (LFU) cache. * An LFU cache evicts the least frequently used item when the cache reaches its capacity. - * It keeps track of how many times each item is used and maintains a doubly linked list - * for efficient addition and removal of items based on their frequency of use. + * It maintains a mapping of keys to nodes, where each node contains the key, its associated value, + * and a frequency count that tracks how many times the item has been accessed. A doubly linked list + * is used to efficiently manage the ordering of items based on their usage frequency. * - * @param The type of keys maintained by this cache. - * @param The type of mapped values. + *

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

* *

* Reference: LFU Cache - Wikipedia *

* + * @param The type of keys maintained by this cache. + * @param The type of mapped values. + * * @author Akshay Dubey (https://github.com/itsAkshayDubey) */ public class LFUCache { @@ -75,7 +80,7 @@ public LFUCache(int capacity) { /** * Retrieves the value associated with the given key from the cache. - * If the key exists, the node's frequency is increased and the node is repositioned + * If the key exists, the node's frequency is incremented, and the node is repositioned * in the linked list based on its updated frequency. * * @param key The key whose associated value is to be returned. diff --git a/src/test/java/com/thealgorithms/datastructures/caches/LFUCacheTest.java b/src/test/java/com/thealgorithms/datastructures/caches/LFUCacheTest.java index 6a94345d625..8ad927564aa 100644 --- a/src/test/java/com/thealgorithms/datastructures/caches/LFUCacheTest.java +++ b/src/test/java/com/thealgorithms/datastructures/caches/LFUCacheTest.java @@ -1,6 +1,8 @@ package com.thealgorithms.datastructures.caches; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; @@ -22,7 +24,7 @@ void testLFUCacheWithIntegerValueShouldPass() { lfuCache.put(6, 60); // will return null as value with key 2 is now evicted - assertEquals(null, lfuCache.get(2)); + assertNull(lfuCache.get(2)); // should return 60 assertEquals(60, lfuCache.get(6)); @@ -30,7 +32,7 @@ void testLFUCacheWithIntegerValueShouldPass() { // this operation will remove value with key as 3 lfuCache.put(7, 70); - assertEquals(null, lfuCache.get(2)); + assertNull(lfuCache.get(2)); assertEquals(70, lfuCache.get(7)); } @@ -41,7 +43,7 @@ void testLFUCacheWithStringValueShouldPass() { lfuCache.put(2, "Beta"); lfuCache.put(3, "Gamma"); lfuCache.put(4, "Delta"); - lfuCache.put(5, "Eplison"); + lfuCache.put(5, "Epsilon"); // get method call will increase frequency of key 1 by 1 assertEquals("Alpha", lfuCache.get(1)); @@ -50,7 +52,7 @@ void testLFUCacheWithStringValueShouldPass() { lfuCache.put(6, "Digamma"); // will return null as value with key 2 is now evicted - assertEquals(null, lfuCache.get(2)); + assertNull(lfuCache.get(2)); // should return string Digamma assertEquals("Digamma", lfuCache.get(6)); @@ -58,25 +60,79 @@ void testLFUCacheWithStringValueShouldPass() { // this operation will remove value with key as 3 lfuCache.put(7, "Zeta"); - assertEquals(null, lfuCache.get(2)); + assertNull(lfuCache.get(2)); assertEquals("Zeta", lfuCache.get(7)); } - /** - * test addNodeWithUpdatedFrequency method - * @author yuluo - */ @Test - void testAddNodeWithUpdatedFrequency() { + void testUpdateValueShouldPreserveFrequency() { LFUCache lfuCache = new LFUCache<>(3); - lfuCache.put(1, "beijing"); - lfuCache.put(2, "shanghai"); - lfuCache.put(3, "gansu"); + lfuCache.put(1, "A"); + lfuCache.put(2, "B"); + lfuCache.put(3, "C"); - assertEquals("beijing", lfuCache.get(1)); + assertEquals("A", lfuCache.get(1)); // Accessing key 1 + lfuCache.put(4, "D"); // This should evict key 2 - lfuCache.put(1, "shanxi"); + assertNull(lfuCache.get(2)); // Key 2 should be evicted + assertEquals("C", lfuCache.get(3)); // Key 3 should still exist + assertEquals("A", lfuCache.get(1)); // Key 1 should still exist - assertEquals("shanxi", lfuCache.get(1)); + lfuCache.put(1, "Updated A"); // Update the value of key 1 + assertEquals("Updated A", lfuCache.get(1)); // Check if the update was successful + } + + @Test + void testEvictionPolicyWhenFull() { + LFUCache lfuCache = new LFUCache<>(2); + lfuCache.put(1, "One"); + lfuCache.put(2, "Two"); + + assertEquals("One", lfuCache.get(1)); // Access key 1 + lfuCache.put(3, "Three"); // This should evict key 2 (least frequently used) + + assertNull(lfuCache.get(2)); // Key 2 should be evicted + assertEquals("One", lfuCache.get(1)); // Key 1 should still exist + assertEquals("Three", lfuCache.get(3)); // Check if key 3 exists + } + + @Test + void testGetFromEmptyCacheShouldReturnNull() { + LFUCache lfuCache = new LFUCache<>(3); + assertNull(lfuCache.get(1)); // Should return null as the cache is empty + } + + @Test + void testPutNullValueShouldStoreNull() { + LFUCache lfuCache = new LFUCache<>(3); + lfuCache.put(1, null); // Store a null value + + assertNull(lfuCache.get(1)); // Should return null + } + + @Test + void testInvalidCacheCapacityShouldThrowException() { + assertThrows(IllegalArgumentException.class, () -> new LFUCache<>(0)); + assertThrows(IllegalArgumentException.class, () -> new LFUCache<>(-1)); + } + + @Test + void testMultipleAccessPatterns() { + LFUCache lfuCache = new LFUCache<>(5); + lfuCache.put(1, "A"); + lfuCache.put(2, "B"); + lfuCache.put(3, "C"); + lfuCache.put(4, "D"); + + assertEquals("A", lfuCache.get(1)); // Access 1 + lfuCache.put(5, "E"); // Should not evict anything yet + lfuCache.put(6, "F"); // Evict B + + assertNull(lfuCache.get(2)); // B should be evicted + assertEquals("C", lfuCache.get(3)); // C should still exist + assertEquals("D", lfuCache.get(4)); // D should still exist + assertEquals("A", lfuCache.get(1)); // A should still exist + assertEquals("E", lfuCache.get(5)); // E should exist + assertEquals("F", lfuCache.get(6)); // F should exist } } From 1b51e3e9886527e8be6d8a2bc968652744c42372 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Thu, 24 Oct 2024 10:48:12 +0530 Subject: [PATCH 54/75] Enhance docs, add more tests in `JohnsonsAlgorithm` (#5964) --- .../graphs/JohnsonsAlgorithm.java | 15 ++- .../graphs/JohnsonsAlgorithmTest.java | 100 ++++++++++-------- 2 files changed, 59 insertions(+), 56 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithm.java index 76c11f78298..351bd5b009e 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithm.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithm.java @@ -21,17 +21,18 @@ */ public final class JohnsonsAlgorithm { - // Constant representing infinity private static final double INF = Double.POSITIVE_INFINITY; - /** - * A private constructor to hide the implicit public one. - */ private JohnsonsAlgorithm() { } /** * Executes Johnson's algorithm on the given graph. + * Steps: + * 1. Add a new vertex to the graph and run Bellman-Ford to compute modified weights + * 2. t the graph using the modified weights + * 3. Run Dijkstra's algorithm for each vertex to compute the shortest paths + * The final result is a 2D array of shortest distances between all pairs of vertices. * * @param graph The input graph represented as an adjacency matrix. * @return A 2D array representing the shortest distances between all pairs of vertices. @@ -40,13 +41,10 @@ public static double[][] johnsonAlgorithm(double[][] graph) { int numVertices = graph.length; double[][] edges = convertToEdgeList(graph); - // Step 1: Add a new vertex and run Bellman-Ford double[] modifiedWeights = bellmanFord(edges, numVertices); - // Step 2: Reweight the graph double[][] reweightedGraph = reweightGraph(graph, modifiedWeights); - // Step 3: Run Dijkstra's algorithm for each vertex double[][] shortestDistances = new double[numVertices][numVertices]; for (int source = 0; source < numVertices; source++) { shortestDistances[source] = dijkstra(reweightedGraph, source, modifiedWeights); @@ -74,7 +72,6 @@ public static double[][] convertToEdgeList(double[][] graph) { } } - // Convert the List to a 2D array return edgeList.toArray(new double[0][]); } @@ -89,7 +86,7 @@ public static double[][] convertToEdgeList(double[][] graph) { private static double[] bellmanFord(double[][] edges, int numVertices) { double[] dist = new double[numVertices + 1]; Arrays.fill(dist, INF); - dist[numVertices] = 0; // Distance to the new source vertex is 0 + dist[numVertices] = 0; // Add edges from the new vertex to all original vertices double[][] allEdges = Arrays.copyOf(edges, edges.length + numVertices); diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithmTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithmTest.java index 0ae837cd944..7ea2449202e 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithmTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithmTest.java @@ -23,114 +23,120 @@ class JohnsonsAlgorithmTest { */ @Test void testSimpleGraph() { - // Test case for a simple graph without negative edges double[][] graph = {{0, 4, INF, INF}, {INF, 0, 1, INF}, {INF, INF, 0, 2}, {INF, INF, INF, 0}}; - double[][] result = JohnsonsAlgorithm.johnsonAlgorithm(graph); - double[][] expected = {{0, 4, 5, 7}, {INF, 0, 1, 3}, {INF, INF, 0, 2}, {INF, INF, INF, 0}}; - assertArrayEquals(expected, result); } /** - * Tests Johnson's Algorithm on a graph with negative edges but no - * negative weight cycles. Verifies the algorithm handles negative - * edge weights correctly. + * Tests Johnson's Algorithm on a graph with negative edges but no negative weight cycles. */ @Test void testGraphWithNegativeEdges() { - // Graph with negative edges but no negative weight cycles double[][] graph = {{0, -1, 4}, {INF, 0, 3}, {INF, INF, 0}}; - double[][] result = JohnsonsAlgorithm.johnsonAlgorithm(graph); - double[][] expected = {{0, INF, 4}, {INF, 0, 3}, {INF, INF, 0}}; - assertArrayEquals(expected, result); } /** - * Tests the behavior of Johnson's Algorithm on a graph with a negative - * weight cycle. Expects an IllegalArgumentException to be thrown - * due to the presence of the cycle. + * Tests Johnson's Algorithm on a graph with a negative weight cycle. */ @Test void testNegativeWeightCycle() { - // Graph with a negative weight cycle double[][] graph = {{0, 1, INF}, {INF, 0, -1}, {-1, INF, 0}}; - - // Johnson's algorithm should throw an exception when a negative cycle is detected - assertThrows(IllegalArgumentException.class, () -> { JohnsonsAlgorithm.johnsonAlgorithm(graph); }); + assertThrows(IllegalArgumentException.class, () -> JohnsonsAlgorithm.johnsonAlgorithm(graph)); } /** - * Tests Dijkstra's algorithm as a part of Johnson's algorithm implementation - * on a small graph. Verifies that the shortest path is correctly calculated. + * Tests Dijkstra's algorithm on a small graph as part of Johnson's Algorithm. */ @Test void testDijkstra() { - // Testing Dijkstra's algorithm with a small graph double[][] graph = {{0, 1, 2}, {INF, 0, 3}, {INF, INF, 0}}; - - double[] modifiedWeights = {0, 0, 0}; // No reweighting in this simple case - + double[] modifiedWeights = {0, 0, 0}; double[] result = JohnsonsAlgorithm.dijkstra(graph, 0, modifiedWeights); double[] expected = {0, 1, 2}; - assertArrayEquals(expected, result); } /** * Tests the conversion of an adjacency matrix to an edge list. - * Verifies that the conversion process generates the correct edge list. */ @Test void testEdgeListConversion() { - // Test the conversion of adjacency matrix to edge list double[][] graph = {{0, 5, INF}, {INF, 0, 2}, {INF, INF, 0}}; - - // Running convertToEdgeList double[][] edges = JohnsonsAlgorithm.convertToEdgeList(graph); - - // Expected edge list: (0 -> 1, weight 5), (1 -> 2, weight 2) double[][] expected = {{0, 1, 5}, {1, 2, 2}}; - - // Verify the edge list matches the expected values assertArrayEquals(expected, edges); } /** - * Tests the reweighting of a graph as a part of Johnson's Algorithm. - * Verifies that the reweighted graph produces correct results. + * Tests the reweighting of a graph. */ @Test void testReweightGraph() { - // Test reweighting of the graph double[][] graph = {{0, 2, 9}, {INF, 0, 1}, {INF, INF, 0}}; - double[] modifiedWeights = {1, 2, 3}; // Arbitrary weight function - + double[] modifiedWeights = {1, 2, 3}; double[][] reweightedGraph = JohnsonsAlgorithm.reweightGraph(graph, modifiedWeights); - - // Expected reweighted graph: double[][] expected = {{0, 1, 7}, {INF, 0, 0}, {INF, INF, 0}}; - assertArrayEquals(expected, reweightedGraph); } /** - * Tests the minDistance method used in Dijkstra's algorithm to find - * the vertex with the minimum distance that has not yet been visited. + * Tests the minDistance method used in Dijkstra's algorithm. */ @Test void testMinDistance() { - // Test minDistance method double[] dist = {INF, 3, 1, INF}; boolean[] visited = {false, false, false, false}; - int minIndex = JohnsonsAlgorithm.minDistance(dist, visited); - - // The vertex with minimum distance is vertex 2 with a distance of 1 assertEquals(2, minIndex); } + + /** + * Tests Johnson's Algorithm on a graph where all vertices are disconnected. + */ + @Test + void testDisconnectedGraph() { + double[][] graph = {{0, INF, INF}, {INF, 0, INF}, {INF, INF, 0}}; + double[][] result = JohnsonsAlgorithm.johnsonAlgorithm(graph); + double[][] expected = {{0, INF, INF}, {INF, 0, INF}, {INF, INF, 0}}; + assertArrayEquals(expected, result); + } + + /** + * Tests Johnson's Algorithm on a fully connected graph. + */ + @Test + void testFullyConnectedGraph() { + double[][] graph = {{0, 1, 2}, {1, 0, 1}, {2, 1, 0}}; + double[][] result = JohnsonsAlgorithm.johnsonAlgorithm(graph); + double[][] expected = {{0, 1, 2}, {1, 0, 1}, {2, 1, 0}}; + assertArrayEquals(expected, result); + } + + /** + * Tests Dijkstra's algorithm on a graph with multiple shortest paths. + */ + @Test + void testDijkstraMultipleShortestPaths() { + double[][] graph = {{0, 1, 2, INF}, {INF, 0, INF, 1}, {INF, INF, 0, 1}, {INF, INF, INF, 0}}; + double[] modifiedWeights = {0, 0, 0, 0}; + double[] result = JohnsonsAlgorithm.dijkstra(graph, 0, modifiedWeights); + double[] expected = {0, 1, 2, 2}; + assertArrayEquals(expected, result); + } + + /** + * Tests Johnson's Algorithm with a graph where all edge weights are zero. + */ + @Test + void testGraphWithZeroWeights() { + double[][] graph = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + double[][] result = JohnsonsAlgorithm.johnsonAlgorithm(graph); + double[][] expected = {{0, INF, INF}, {INF, 0, INF}, {INF, INF, 0}}; + assertArrayEquals(expected, result); + } } From 3a9a2c4160747d5219f007e51155e5b8d9bf2625 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Thu, 24 Oct 2024 10:52:24 +0530 Subject: [PATCH 55/75] Enhance docs, add more tests in `HamiltonianCycle` (#5963) --- .../graphs/HamiltonianCycle.java | 65 +++++++++---------- .../graphs/HamiltonianCycleTest.java | 61 ++++++++++++++++- 2 files changed, 92 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java b/src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java index f12e3892b1b..5c95850c497 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java @@ -1,8 +1,14 @@ package com.thealgorithms.datastructures.graphs; +import java.util.Arrays; + /** - * Java program for Hamiltonian Cycle - * wikipedia + * Java program to find a Hamiltonian Cycle in a graph. + * A Hamiltonian Cycle is a cycle that visits every vertex exactly once + * and returns to the starting vertex. + * + *

For more details, see the + * Wikipedia article. * * @author Akshay Dubey */ @@ -14,30 +20,30 @@ public class HamiltonianCycle { private int[][] graph; /** - * Find hamiltonian cycle for given graph G(V,E) + * Finds a Hamiltonian Cycle for the given graph. * - * @param graph Adjacency matrix of a graph G(V, E) - * for which hamiltonian path is to be found - * @return Array containing hamiltonian cycle - * else returns 1D array with value -1. + * @param graph Adjacency matrix representing the graph G(V, E), where V is + * the set of vertices and E is the set of edges. + * @return An array representing the Hamiltonian cycle if found, otherwise an + * array filled with -1 indicating no Hamiltonian cycle exists. */ public int[] findHamiltonianCycle(int[][] graph) { + // Single vertex graph + if (graph.length == 1) { + return new int[] {0, 0}; + } + this.vertex = graph.length; this.cycle = new int[this.vertex + 1]; - // Initialize path array with -1 value - for (int i = 0; i < this.cycle.length; i++) { - this.cycle[i] = -1; - } + // Initialize the cycle array with -1 to represent unvisited vertices + Arrays.fill(this.cycle, -1); this.graph = graph; - this.cycle[0] = 0; this.pathCount = 1; if (!isPathFound(0)) { - for (int i = 0; i < this.cycle.length; i++) { - this.cycle[i] = -1; - } + Arrays.fill(this.cycle, -1); } else { this.cycle[this.cycle.length - 1] = this.cycle[0]; } @@ -46,11 +52,10 @@ public int[] findHamiltonianCycle(int[][] graph) { } /** - * function to find paths recursively - * Find paths recursively from given vertex + * Recursively searches for a Hamiltonian cycle from the given vertex. * - * @param vertex Vertex from which path is to be found - * @returns true if path is found false otherwise + * @param vertex The current vertex from which to explore paths. + * @return {@code true} if a Hamiltonian cycle is found, otherwise {@code false}. */ public boolean isPathFound(int vertex) { boolean isLastVertexConnectedToStart = this.graph[vertex][0] == 1 && this.pathCount == this.vertex; @@ -58,31 +63,26 @@ public boolean isPathFound(int vertex) { return true; } - /* all vertices selected but last vertex not linked to 0 **/ + // If all vertices are visited but the last vertex is not connected to the start if (this.pathCount == this.vertex) { return false; } for (int v = 0; v < this.vertex; v++) { - /* if connected **/ - if (this.graph[vertex][v] == 1) { - /* add to path **/ - this.cycle[this.pathCount++] = v; - - /* remove connection **/ + if (this.graph[vertex][v] == 1) { // Check if there is an edge + this.cycle[this.pathCount++] = v; // Add the vertex to the cycle this.graph[vertex][v] = 0; this.graph[v][vertex] = 0; - /* if vertex not already selected solve recursively **/ + // Recursively attempt to complete the cycle if (!isPresent(v)) { return isPathFound(v); } - /* restore connection **/ + // Restore the edge if the path does not work this.graph[vertex][v] = 1; this.graph[v][vertex] = 1; - /* remove path **/ this.cycle[--this.pathCount] = -1; } } @@ -90,10 +90,10 @@ public boolean isPathFound(int vertex) { } /** - * function to check if path is already selected - * Check if path is already selected + * Checks if a vertex is already part of the current Hamiltonian path. * - * @param vertex Starting vertex + * @param vertex The vertex to check. + * @return {@code true} if the vertex is already in the path, otherwise {@code false}. */ public boolean isPresent(int vertex) { for (int i = 0; i < pathCount - 1; i++) { @@ -101,7 +101,6 @@ public boolean isPresent(int vertex) { return true; } } - return false; } } diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/HamiltonianCycleTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/HamiltonianCycleTest.java index ed7c886d784..4529606d779 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/HamiltonianCycleTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/HamiltonianCycleTest.java @@ -6,7 +6,7 @@ class HamiltonianCycleTest { - private HamiltonianCycle hamiltonianCycle = new HamiltonianCycle(); + private final HamiltonianCycle hamiltonianCycle = new HamiltonianCycle(); @Test void testFindHamiltonianCycleShouldReturnHamiltonianCycle() { @@ -36,4 +36,63 @@ void testFindHamiltonianCycleShouldReturnInfinityArray() { assertArrayEquals(expectedArray, hamiltonianCycle.findHamiltonianCycle(inputArray)); } + + @Test + void testSingleVertexGraph() { + int[] expectedArray = {0, 0}; + int[][] inputArray = {{0}}; + + assertArrayEquals(expectedArray, hamiltonianCycle.findHamiltonianCycle(inputArray)); + } + + @Test + void testDisconnectedGraphShouldReturnInfinityArray() { + int[] expectedArray = {-1, -1, -1, -1, -1}; + int[][] inputArray = {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}; + + assertArrayEquals(expectedArray, hamiltonianCycle.findHamiltonianCycle(inputArray)); + } + + @Test + void testCompleteGraphShouldReturnHamiltonianCycle() { + int[] expectedArray = {0, 1, 2, 3, 4, 0}; + int[][] inputArray = { + {0, 1, 1, 1, 1}, + {1, 0, 1, 1, 1}, + {1, 1, 0, 1, 1}, + {1, 1, 1, 0, 1}, + {1, 1, 1, 1, 0}, + }; + + assertArrayEquals(expectedArray, hamiltonianCycle.findHamiltonianCycle(inputArray)); + } + + @Test + void testGraphWithNoEdgesShouldReturnInfinityArray() { + int[] expectedArray = {-1, -1, -1, -1, -1, -1}; + + int[][] inputArray = { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + }; + + assertArrayEquals(expectedArray, hamiltonianCycle.findHamiltonianCycle(inputArray)); + } + + @Test + void testLargeGraphWithHamiltonianCycle() { + int[] expectedArray = {0, 1, 2, 3, 4, 0}; + int[][] inputArray = { + {0, 1, 0, 1, 1}, + {1, 0, 1, 1, 0}, + {0, 1, 0, 1, 1}, + {1, 1, 1, 0, 1}, + {1, 0, 1, 1, 0}, + }; + + assertArrayEquals(expectedArray, hamiltonianCycle.findHamiltonianCycle(inputArray)); + } } From 13be2501c29c0997c511f39aa9fe33957a1ea132 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Thu, 24 Oct 2024 10:59:16 +0530 Subject: [PATCH 56/75] Enhance docs, add tests in KahnsAlgorithm (#5965) --- DIRECTORY.md | 1 + .../datastructures/graphs/KahnsAlgorithm.java | 84 ++++++++++++------- .../graphs/KahnsAlgorithmTest.java | 77 +++++++++++++++++ 3 files changed, 134 insertions(+), 28 deletions(-) create mode 100644 src/test/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithmTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 4f427686092..0539f0df135 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -810,6 +810,7 @@ * [FordFulkersonTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/FordFulkersonTest.java) * [HamiltonianCycleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/HamiltonianCycleTest.java) * [JohnsonsAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithmTest.java) + * [KahnsAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithmTest.java) * [KosarajuTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/KosarajuTest.java) * [TarjansAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithmTest.java) * [WelshPowellTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/WelshPowellTest.java) diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java index d5035cf625a..9a97bc3f480 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java @@ -9,96 +9,120 @@ import java.util.Set; /** - * A class that represents the adjaceny list of a graph + * A class representing the adjacency list of a directed graph. The adjacency list + * maintains a mapping of vertices to their adjacent vertices. + * + * @param the type of vertices, extending Comparable to ensure that vertices + * can be compared */ class AdjacencyList> { Map> adj; + /** + * Constructor to initialize the adjacency list. + */ AdjacencyList() { - adj = new LinkedHashMap>(); + adj = new LinkedHashMap<>(); } /** - * This function adds an Edge to the adjaceny list + * Adds a directed edge from one vertex to another in the adjacency list. + * If the vertex does not exist, it will be added to the list. * - * @param from , the vertex the edge is from - * @param to, the vertex the edge is going to + * @param from the starting vertex of the directed edge + * @param to the destination vertex of the directed edge */ void addEdge(E from, E to) { - try { - adj.get(from).add(to); - } catch (Exception E) { - adj.put(from, new ArrayList()); - adj.get(from).add(to); + if (!adj.containsKey(from)) { + adj.put(from, new ArrayList<>()); } + adj.get(from).add(to); if (!adj.containsKey(to)) { - adj.put(to, new ArrayList()); + adj.put(to, new ArrayList<>()); } } /** - * @param v, A vertex in a graph - * @return returns an ArrayList of all the adjacents of vertex v + * Retrieves the list of adjacent vertices for a given vertex. + * + * @param v the vertex whose adjacent vertices are to be fetched + * @return an ArrayList of adjacent vertices for vertex v */ ArrayList getAdjacents(E v) { return adj.get(v); } /** - * @return returns a set of all vertices in the graph + * Retrieves the set of all vertices present in the graph. + * + * @return a set containing all vertices in the graph */ Set getVertices() { return adj.keySet(); } } +/** + * A class that performs topological sorting on a directed graph using Kahn's algorithm. + * + * @param the type of vertices, extending Comparable to ensure that vertices + * can be compared + */ class TopologicalSort> { AdjacencyList graph; Map inDegree; + /** + * Constructor to initialize the topological sorting class with a given graph. + * + * @param graph the directed graph represented as an adjacency list + */ TopologicalSort(AdjacencyList graph) { this.graph = graph; } /** - * Calculates the in degree of all vertices + * Calculates the in-degree of all vertices in the graph. The in-degree is + * the number of edges directed into a vertex. */ void calculateInDegree() { inDegree = new HashMap<>(); for (E vertex : graph.getVertices()) { - if (!inDegree.containsKey(vertex)) { - inDegree.put(vertex, 0); - } + inDegree.putIfAbsent(vertex, 0); for (E adjacent : graph.getAdjacents(vertex)) { - try { - inDegree.put(adjacent, inDegree.get(adjacent) + 1); - } catch (Exception e) { - inDegree.put(adjacent, 1); - } + inDegree.put(adjacent, inDegree.getOrDefault(adjacent, 0) + 1); } } } /** - * Returns an ArrayList with vertices arranged in topological order + * Returns an ArrayList containing the vertices of the graph arranged in + * topological order. Topological sorting ensures that for any directed edge + * (u, v), vertex u appears before vertex v in the ordering. + * + * @return an ArrayList of vertices in topological order + * @throws IllegalStateException if the graph contains a cycle */ ArrayList topSortOrder() { calculateInDegree(); - Queue q = new LinkedList(); + Queue q = new LinkedList<>(); - for (final var entry : inDegree.entrySet()) { + for (var entry : inDegree.entrySet()) { if (entry.getValue() == 0) { q.add(entry.getKey()); } } ArrayList answer = new ArrayList<>(); + int processedVertices = 0; while (!q.isEmpty()) { E current = q.poll(); answer.add(current); + processedVertices++; + for (E adjacent : graph.getAdjacents(current)) { inDegree.put(adjacent, inDegree.get(adjacent) - 1); if (inDegree.get(adjacent) == 0) { @@ -107,12 +131,16 @@ ArrayList topSortOrder() { } } + if (processedVertices != graph.getVertices().size()) { + throw new IllegalStateException("Graph contains a cycle, topological sort not possible"); + } + return answer; } } /** - * A driver class that sorts a given graph in topological order. + * A driver class that sorts a given graph in topological order using Kahn's algorithm. */ public final class KahnsAlgorithm { private KahnsAlgorithm() { @@ -130,7 +158,7 @@ public static void main(String[] args) { TopologicalSort topSort = new TopologicalSort<>(graph); - // Printing the order + // Printing the topological order for (String s : topSort.topSortOrder()) { System.out.print(s + " "); } diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithmTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithmTest.java new file mode 100644 index 00000000000..8d096a4b445 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithmTest.java @@ -0,0 +1,77 @@ +package com.thealgorithms.datastructures.graphs; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import org.junit.jupiter.api.Test; + +class KahnsAlgorithmTest { + + @Test + void testBasicGraph() { + // Test case with a basic directed acyclic graph (DAG) + AdjacencyList graph = new AdjacencyList<>(); + graph.addEdge("a", "b"); + graph.addEdge("c", "a"); + graph.addEdge("a", "d"); + graph.addEdge("b", "d"); + + TopologicalSort topSort = new TopologicalSort<>(graph); + ArrayList result = topSort.topSortOrder(); + + String[] expectedOrder = {"c", "a", "b", "d"}; + assertArrayEquals(expectedOrder, result.toArray()); + } + + @Test + void testGraphWithMultipleSources() { + // Test case where graph has multiple independent sources + AdjacencyList graph = new AdjacencyList<>(); + graph.addEdge("a", "c"); + graph.addEdge("b", "c"); + + TopologicalSort topSort = new TopologicalSort<>(graph); + ArrayList result = topSort.topSortOrder(); + + String[] expectedOrder = {"a", "b", "c"}; + assertArrayEquals(expectedOrder, result.toArray()); + } + + @Test + void testDisconnectedGraph() { + // Test case for disconnected graph + AdjacencyList graph = new AdjacencyList<>(); + graph.addEdge("a", "b"); + graph.addEdge("c", "d"); + + TopologicalSort topSort = new TopologicalSort<>(graph); + ArrayList result = topSort.topSortOrder(); + + String[] expectedOrder = {"a", "c", "b", "d"}; + assertArrayEquals(expectedOrder, result.toArray()); + } + + @Test + void testGraphWithCycle() { + // Test case for a graph with a cycle - topological sorting is not possible + AdjacencyList graph = new AdjacencyList<>(); + graph.addEdge("a", "b"); + graph.addEdge("b", "c"); + graph.addEdge("c", "a"); + + TopologicalSort topSort = new TopologicalSort<>(graph); + + assertThrows(IllegalStateException.class, () -> topSort.topSortOrder()); + } + + @Test + void testSingleNodeGraph() { + AdjacencyList graph = new AdjacencyList<>(); + graph.addEdge("a", "a"); // self-loop + + TopologicalSort topSort = new TopologicalSort<>(graph); + + assertThrows(IllegalStateException.class, () -> topSort.topSortOrder()); + } +} From 578e5a73df2c2499f5e2b38cddb58138f72eebc0 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Thu, 24 Oct 2024 11:08:08 +0530 Subject: [PATCH 57/75] Enhance docs, add more tests in `Kosaraju` (#5966) --- .../datastructures/graphs/Kosaraju.java | 155 ++++++++++-------- .../datastructures/graphs/KosarajuTest.java | 74 ++++++--- 2 files changed, 143 insertions(+), 86 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java b/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java index c5f15839f99..78a184f042b 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java @@ -5,82 +5,91 @@ import java.util.Stack; /** - * Java program that implements Kosaraju Algorithm. - * @author Shivanagouda S A + * This class implements the Kosaraju Algorithm to find all the Strongly Connected Components (SCCs) + * of a directed graph. Kosaraju's algorithm runs in linear time and leverages the concept that + * the SCCs of a directed graph remain the same in its transpose (reverse) graph. + * *

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

+ * + *

Algorithm:

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

Example Graph:

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

SCCs in the example:

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

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

+ * + *

Graph Transpose Example:

+ *
+ * 0 ---> 2 <------- 3 <------- 4 <------ 7
+ * ^     /                      ^ \       /
+ * |    /                       |  \     /
+ * |   /                        |   \   /
+ * |  v                         |    v v
+ * 1                            5 <--- 6
+ * 
+ * + * The SCCs of this transpose graph are the same as the original graph. */ - public class Kosaraju { - // Sort edges according to lowest finish time - Stack stack = new Stack(); + // Stack to sort edges by the lowest finish time (used in the first DFS) + private final Stack stack = new Stack<>(); - // Store each component + // Store each strongly connected component private List scc = new ArrayList<>(); - // All the strongly connected components - private List> sccsList = new ArrayList<>(); + // List of all SCCs + private final List> sccsList = new ArrayList<>(); /** + * Main function to perform Kosaraju's Algorithm. + * Steps: + * 1. Sort nodes by the lowest finishing time + * 2. Create the transpose (reverse edges) of the original graph + * 3. Find SCCs by performing DFS on the transpose graph + * 4. Return the list of SCCs * - * @param v Node count - * @param list Adjacency list of graph - * @return List of SCCs + * @param v the number of vertices in the graph + * @param list the adjacency list representing the directed graph + * @return a list of SCCs where each SCC is a list of vertices */ public List> kosaraju(int v, List> list) { - sortEdgesByLowestFinishTime(v, list); - List> transposeGraph = createTransposeMatrix(v, list); - findStronglyConnectedComponents(v, transposeGraph); - return sccsList; } + /** + * Performs DFS on the original graph to sort nodes by their finishing times. + * @param v the number of vertices in the graph + * @param list the adjacency list representing the original graph + */ private void sortEdgesByLowestFinishTime(int v, List> list) { int[] vis = new int[v]; for (int i = 0; i < v; i++) { @@ -90,8 +99,14 @@ private void sortEdgesByLowestFinishTime(int v, List> list) { } } + /** + * Creates the transpose (reverse) of the original graph. + * @param v the number of vertices in the graph + * @param list the adjacency list representing the original graph + * @return the adjacency list representing the transposed graph + */ private List> createTransposeMatrix(int v, List> list) { - var transposeGraph = new ArrayList>(v); + List> transposeGraph = new ArrayList<>(v); for (int i = 0; i < v; i++) { transposeGraph.add(new ArrayList<>()); } @@ -104,14 +119,14 @@ private List> createTransposeMatrix(int v, List> lis } /** - * - * @param v Node count - * @param transposeGraph Transpose of the given adjacency list + * Finds the strongly connected components (SCCs) by performing DFS on the transposed graph. + * @param v the number of vertices in the graph + * @param transposeGraph the adjacency list representing the transposed graph */ public void findStronglyConnectedComponents(int v, List> transposeGraph) { int[] vis = new int[v]; while (!stack.isEmpty()) { - var node = stack.pop(); + int node = stack.pop(); if (vis[node] == 0) { dfs2(node, vis, transposeGraph); sccsList.add(scc); @@ -120,7 +135,12 @@ public void findStronglyConnectedComponents(int v, List> transpose } } - // Dfs to store the nodes in order of lowest finish time + /** + * Performs DFS on the original graph and pushes nodes onto the stack in order of their finish time. + * @param node the current node being visited + * @param vis array to keep track of visited nodes + * @param list the adjacency list of the graph + */ private void dfs(int node, int[] vis, List> list) { vis[node] = 1; for (Integer neighbour : list.get(node)) { @@ -131,7 +151,12 @@ private void dfs(int node, int[] vis, List> list) { stack.push(node); } - // Dfs to find all the nodes of each strongly connected component + /** + * Performs DFS on the transposed graph to find the strongly connected components. + * @param node the current node being visited + * @param vis array to keep track of visited nodes + * @param list the adjacency list of the transposed graph + */ private void dfs2(int node, int[] vis, List> list) { vis[node] = 1; for (Integer neighbour : list.get(node)) { diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/KosarajuTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/KosarajuTest.java index c1e68acac2e..53ed26dff26 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/KosarajuTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/KosarajuTest.java @@ -1,6 +1,6 @@ package com.thealgorithms.datastructures.graphs; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.ArrayList; import java.util.Arrays; @@ -9,14 +9,13 @@ public class KosarajuTest { - private Kosaraju kosaraju = new Kosaraju(); + private final Kosaraju kosaraju = new Kosaraju(); @Test - public void findStronglyConnectedComps() { - // Create a adjacency list of graph - var n = 8; - var adjList = new ArrayList>(n); - + public void testFindStronglyConnectedComponents() { + // Create a graph using adjacency list + int n = 8; + List> adjList = new ArrayList<>(n); for (int i = 0; i < n; i++) { adjList.add(new ArrayList<>()); } @@ -36,24 +35,24 @@ public void findStronglyConnectedComps() { List> expectedResult = new ArrayList<>(); /* Expected result: - 0, 1, 2 - 3 - 5, 4, 6 - 7 + {0, 1, 2} + {3} + {5, 4, 6} + {7} */ expectedResult.add(Arrays.asList(1, 2, 0)); - expectedResult.add(Arrays.asList(3)); + expectedResult.add(List.of(3)); expectedResult.add(Arrays.asList(5, 6, 4)); - expectedResult.add(Arrays.asList(7)); - assertTrue(expectedResult.equals(actualResult)); + expectedResult.add(List.of(7)); + + assertEquals(expectedResult, actualResult); } @Test - public void findStronglyConnectedCompsShouldGetSingleNodes() { - // Create a adjacency list of graph - var n = 8; - var adjList = new ArrayList>(n); - + public void testFindSingleNodeSCC() { + // Create a simple graph using adjacency list + int n = 8; + List> adjList = new ArrayList<>(n); for (int i = 0; i < n; i++) { adjList.add(new ArrayList<>()); } @@ -71,9 +70,42 @@ public void findStronglyConnectedCompsShouldGetSingleNodes() { List> expectedResult = new ArrayList<>(); /* Expected result: - 0, 1, 2, 3, 4, 5, 6, 7 + {0, 1, 2, 3, 4, 5, 6, 7} */ expectedResult.add(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 0)); - assertTrue(expectedResult.equals(actualResult)); + + assertEquals(expectedResult, actualResult); + } + + @Test + public void testDisconnectedGraph() { + // Create a disconnected graph (two separate components) + int n = 5; + List> adjList = new ArrayList<>(n); + for (int i = 0; i < n; i++) { + adjList.add(new ArrayList<>()); + } + + // Add edges for first component + adjList.get(0).add(1); + adjList.get(1).add(2); + adjList.get(2).add(0); + + // Add edges for second component + adjList.get(3).add(4); + adjList.get(4).add(3); + + List> actualResult = kosaraju.kosaraju(n, adjList); + + List> expectedResult = new ArrayList<>(); + /* + Expected result: + {0, 1, 2} + {3, 4} + */ + expectedResult.add(Arrays.asList(4, 3)); + expectedResult.add(Arrays.asList(1, 2, 0)); + + assertEquals(expectedResult, actualResult); } } From 0feb41618872014f0809942ca1def0978eac1225 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Thu, 24 Oct 2024 11:11:55 +0530 Subject: [PATCH 58/75] Enhance docs, add tests in `Kruskal` (#5967) --- DIRECTORY.md | 1 + .../datastructures/graphs/Kruskal.java | 107 ++++++++--------- .../datastructures/graphs/KruskalTest.java | 112 ++++++++++++++++++ 3 files changed, 161 insertions(+), 59 deletions(-) create mode 100644 src/test/java/com/thealgorithms/datastructures/graphs/KruskalTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 0539f0df135..1def3e25c06 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -812,6 +812,7 @@ * [JohnsonsAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithmTest.java) * [KahnsAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithmTest.java) * [KosarajuTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/KosarajuTest.java) + * [KruskalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/KruskalTest.java) * [TarjansAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithmTest.java) * [WelshPowellTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/WelshPowellTest.java) * hashmap diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java b/src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java index eb5b65d5c0d..25c4548daa7 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java @@ -1,27 +1,34 @@ package com.thealgorithms.datastructures.graphs; -// Problem -> Connect all the edges with the minimum cost. -// Possible Solution -> Kruskal Algorithm (KA), KA finds the minimum-spanning-tree, which means, the -// group of edges with the minimum sum of their weights that connect the whole graph. -// The graph needs to be connected, because if there are nodes impossible to reach, there are no -// edges that could connect every node in the graph. -// KA is a Greedy Algorithm, because edges are analysed based on their weights, that is why a -// Priority Queue is used, to take first those less weighted. -// This implementations below has some changes compared to conventional ones, but they are explained -// all along the code. import java.util.Comparator; import java.util.HashSet; import java.util.PriorityQueue; +/** + * The Kruskal class implements Kruskal's Algorithm to find the Minimum Spanning Tree (MST) + * of a connected, undirected graph. The algorithm constructs the MST by selecting edges + * with the least weight, ensuring no cycles are formed, and using union-find to track the + * connected components. + * + *

Key Features:

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

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

+ */ public class Kruskal { - // Complexity: O(E log V) time, where E is the number of edges in the graph and V is the number - // of vertices - private static class Edge { + /** + * Represents an edge in the graph with a source, destination, and weight. + */ + static class Edge { - private int from; - private int to; - private int weight; + int from; + int to; + int weight; Edge(int from, int to, int weight) { this.from = from; @@ -30,51 +37,30 @@ private static class Edge { } } - private static void addEdge(HashSet[] graph, int from, int to, int weight) { + /** + * Adds an edge to the graph. + * + * @param graph the adjacency list representing the graph + * @param from the source vertex of the edge + * @param to the destination vertex of the edge + * @param weight the weight of the edge + */ + static void addEdge(HashSet[] graph, int from, int to, int weight) { graph[from].add(new Edge(from, to, weight)); } - public static void main(String[] args) { - HashSet[] graph = new HashSet[7]; - for (int i = 0; i < graph.length; i++) { - graph[i] = new HashSet<>(); - } - addEdge(graph, 0, 1, 2); - addEdge(graph, 0, 2, 3); - addEdge(graph, 0, 3, 3); - addEdge(graph, 1, 2, 4); - addEdge(graph, 2, 3, 5); - addEdge(graph, 1, 4, 3); - addEdge(graph, 2, 4, 1); - addEdge(graph, 3, 5, 7); - addEdge(graph, 4, 5, 8); - addEdge(graph, 5, 6, 9); - - System.out.println("Initial Graph: "); - for (int i = 0; i < graph.length; i++) { - for (Edge edge : graph[i]) { - System.out.println(i + " <-- weight " + edge.weight + " --> " + edge.to); - } - } - - Kruskal k = new Kruskal(); - HashSet[] solGraph = k.kruskal(graph); - - System.out.println("\nMinimal Graph: "); - for (int i = 0; i < solGraph.length; i++) { - for (Edge edge : solGraph[i]) { - System.out.println(i + " <-- weight " + edge.weight + " --> " + edge.to); - } - } - } - + /** + * Kruskal's algorithm to find the Minimum Spanning Tree (MST) of a graph. + * + * @param graph the adjacency list representing the input graph + * @return the adjacency list representing the MST + */ public HashSet[] kruskal(HashSet[] graph) { int nodes = graph.length; - int[] captain = new int[nodes]; - // captain of i, stores the set with all the connected nodes to i + int[] captain = new int[nodes]; // Stores the "leader" of each node's connected component HashSet[] connectedGroups = new HashSet[nodes]; HashSet[] minGraph = new HashSet[nodes]; - PriorityQueue edges = new PriorityQueue<>((Comparator.comparingInt(edge -> edge.weight))); + PriorityQueue edges = new PriorityQueue<>(Comparator.comparingInt(edge -> edge.weight)); for (int i = 0; i < nodes; i++) { minGraph[i] = new HashSet<>(); connectedGroups[i] = new HashSet<>(); @@ -83,18 +69,21 @@ public HashSet[] kruskal(HashSet[] graph) { edges.addAll(graph[i]); } int connectedElements = 0; - // as soon as two sets merge all the elements, the algorithm must stop while (connectedElements != nodes && !edges.isEmpty()) { Edge edge = edges.poll(); - // This if avoids cycles + + // Avoid forming cycles by checking if the nodes belong to different connected components if (!connectedGroups[captain[edge.from]].contains(edge.to) && !connectedGroups[captain[edge.to]].contains(edge.from)) { - // merge sets of the captains of each point connected by the edge + // Merge the two sets of nodes connected by the edge connectedGroups[captain[edge.from]].addAll(connectedGroups[captain[edge.to]]); - // update captains of the elements merged + + // Update the captain for each merged node connectedGroups[captain[edge.from]].forEach(i -> captain[i] = captain[edge.from]); - // add Edge to minimal graph + + // Add the edge to the resulting MST graph addEdge(minGraph, edge.from, edge.to, edge.weight); - // count how many elements have been merged + + // Update the count of connected nodes connectedElements = connectedGroups[captain[edge.from]].size(); } } diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/KruskalTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/KruskalTest.java new file mode 100644 index 00000000000..b18f161ef1a --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/graphs/KruskalTest.java @@ -0,0 +1,112 @@ +package com.thealgorithms.datastructures.graphs; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class KruskalTest { + + private Kruskal kruskal; + private HashSet[] graph; + + @BeforeEach + public void setUp() { + kruskal = new Kruskal(); + int n = 7; + graph = new HashSet[n]; + for (int i = 0; i < n; i++) { + graph[i] = new HashSet<>(); + } + + // Add edges to the graph + Kruskal.addEdge(graph, 0, 1, 2); + Kruskal.addEdge(graph, 0, 2, 3); + Kruskal.addEdge(graph, 0, 3, 3); + Kruskal.addEdge(graph, 1, 2, 4); + Kruskal.addEdge(graph, 2, 3, 5); + Kruskal.addEdge(graph, 1, 4, 3); + Kruskal.addEdge(graph, 2, 4, 1); + Kruskal.addEdge(graph, 3, 5, 7); + Kruskal.addEdge(graph, 4, 5, 8); + Kruskal.addEdge(graph, 5, 6, 9); + } + + @Test + public void testKruskal() { + int n = 6; + HashSet[] graph = new HashSet[n]; + + for (int i = 0; i < n; i++) { + graph[i] = new HashSet<>(); + } + + Kruskal.addEdge(graph, 0, 1, 4); + Kruskal.addEdge(graph, 0, 2, 2); + Kruskal.addEdge(graph, 1, 2, 1); + Kruskal.addEdge(graph, 1, 3, 5); + Kruskal.addEdge(graph, 2, 3, 8); + Kruskal.addEdge(graph, 2, 4, 10); + Kruskal.addEdge(graph, 3, 4, 2); + Kruskal.addEdge(graph, 3, 5, 6); + Kruskal.addEdge(graph, 4, 5, 3); + + HashSet[] result = kruskal.kruskal(graph); + + List> actualEdges = new ArrayList<>(); + for (HashSet edges : result) { + for (Kruskal.Edge edge : edges) { + actualEdges.add(Arrays.asList(edge.from, edge.to, edge.weight)); + } + } + + List> expectedEdges = Arrays.asList(Arrays.asList(1, 2, 1), Arrays.asList(0, 2, 2), Arrays.asList(3, 4, 2), Arrays.asList(4, 5, 3), Arrays.asList(1, 3, 5)); + + assertTrue(actualEdges.containsAll(expectedEdges) && expectedEdges.containsAll(actualEdges)); + } + + @Test + public void testEmptyGraph() { + HashSet[] emptyGraph = new HashSet[0]; + HashSet[] result = kruskal.kruskal(emptyGraph); + assertEquals(0, result.length); + } + + @Test + public void testSingleNodeGraph() { + HashSet[] singleNodeGraph = new HashSet[1]; + singleNodeGraph[0] = new HashSet<>(); + HashSet[] result = kruskal.kruskal(singleNodeGraph); + assertTrue(result[0].isEmpty()); + } + + @Test + public void testGraphWithDisconnectedNodes() { + int n = 5; + HashSet[] disconnectedGraph = new HashSet[n]; + for (int i = 0; i < n; i++) { + disconnectedGraph[i] = new HashSet<>(); + } + + Kruskal.addEdge(disconnectedGraph, 0, 1, 2); + Kruskal.addEdge(disconnectedGraph, 2, 3, 4); + + HashSet[] result = kruskal.kruskal(disconnectedGraph); + + List> actualEdges = new ArrayList<>(); + for (HashSet edges : result) { + for (Kruskal.Edge edge : edges) { + actualEdges.add(Arrays.asList(edge.from, edge.to, edge.weight)); + } + } + + List> expectedEdges = Arrays.asList(Arrays.asList(0, 1, 2), Arrays.asList(2, 3, 4)); + + assertTrue(actualEdges.containsAll(expectedEdges) && expectedEdges.containsAll(actualEdges)); + } +} From 5246f635797194f5f0d7ac93af61099a5764fa4f Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Thu, 24 Oct 2024 11:20:12 +0530 Subject: [PATCH 59/75] Enhance docs, remove `main`. add more tests (#5925) --- .../conversions/IntegerToRoman.java | 96 +++++++++---------- .../conversions/IntegerToRomanTest.java | 25 +++++ 2 files changed, 73 insertions(+), 48 deletions(-) diff --git a/src/main/java/com/thealgorithms/conversions/IntegerToRoman.java b/src/main/java/com/thealgorithms/conversions/IntegerToRoman.java index 9c031df9504..fec437668fe 100644 --- a/src/main/java/com/thealgorithms/conversions/IntegerToRoman.java +++ b/src/main/java/com/thealgorithms/conversions/IntegerToRoman.java @@ -1,68 +1,68 @@ package com.thealgorithms.conversions; /** - * Converting Integers into Roman Numerals + * A utility class to convert integers into Roman numerals. * - *

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

Roman numerals follow these rules: + *

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

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

+ *   1994 -> MCMXCIV (1000 + 900 + 90 + 4)
+ * 
*/ public final class IntegerToRoman { + + // Array of Roman numeral values in descending order + private static final int[] ALL_ROMAN_NUMBERS_IN_ARABIC = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}; + + // Corresponding Roman numeral symbols + private static final String[] ALL_ROMAN_NUMBERS = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"}; + private IntegerToRoman() { } - private static final int[] ALL_ROMAN_NUMBERS_IN_ARABIC = new int[] { - 1000, - 900, - 500, - 400, - 100, - 90, - 50, - 40, - 10, - 9, - 5, - 4, - 1, - }; - private static final String[] ALL_ROMAN_NUMBERS = new String[] { - "M", - "CM", - "D", - "CD", - "C", - "XC", - "L", - "XL", - "X", - "IX", - "V", - "IV", - "I", - }; - - // Value must be > 0 + /** + * Converts an integer to its Roman numeral representation. + * Steps: + *
    + *
  1. Iterate over the Roman numeral values in descending order
  2. + *
  3. Calculate how many times a numeral fits
  4. + *
  5. Append the corresponding symbol
  6. + *
  7. Subtract the value from the number
  8. + *
  9. Repeat until the number is zero
  10. + *
  11. Return the Roman numeral representation
  12. + *
+ * + * @param num the integer value to convert (must be greater than 0) + * @return the Roman numeral representation of the input integer + * or an empty string if the input is non-positive + */ public static String integerToRoman(int num) { if (num <= 0) { return ""; } StringBuilder builder = new StringBuilder(); - - for (int a = 0; a < ALL_ROMAN_NUMBERS_IN_ARABIC.length; a++) { - int times = num / ALL_ROMAN_NUMBERS_IN_ARABIC[a]; - for (int b = 0; b < times; b++) { - builder.append(ALL_ROMAN_NUMBERS[a]); - } - - num -= times * ALL_ROMAN_NUMBERS_IN_ARABIC[a]; + for (int i = 0; i < ALL_ROMAN_NUMBERS_IN_ARABIC.length; i++) { + int times = num / ALL_ROMAN_NUMBERS_IN_ARABIC[i]; + builder.append(ALL_ROMAN_NUMBERS[i].repeat(Math.max(0, times))); + num -= times * ALL_ROMAN_NUMBERS_IN_ARABIC[i]; } return builder.toString(); } - - public static void main(String[] args) { - System.out.println(IntegerToRoman.integerToRoman(2131)); - } } diff --git a/src/test/java/com/thealgorithms/conversions/IntegerToRomanTest.java b/src/test/java/com/thealgorithms/conversions/IntegerToRomanTest.java index 04768d034b9..181cad93028 100644 --- a/src/test/java/com/thealgorithms/conversions/IntegerToRomanTest.java +++ b/src/test/java/com/thealgorithms/conversions/IntegerToRomanTest.java @@ -10,5 +10,30 @@ public class IntegerToRomanTest { public void testIntegerToRoman() { assertEquals("MCMXCIV", IntegerToRoman.integerToRoman(1994)); assertEquals("LVIII", IntegerToRoman.integerToRoman(58)); + assertEquals("IV", IntegerToRoman.integerToRoman(4)); + assertEquals("IX", IntegerToRoman.integerToRoman(9)); + assertEquals("MMM", IntegerToRoman.integerToRoman(3000)); + } + + @Test + public void testSmallNumbers() { + assertEquals("I", IntegerToRoman.integerToRoman(1)); + assertEquals("II", IntegerToRoman.integerToRoman(2)); + assertEquals("III", IntegerToRoman.integerToRoman(3)); + } + + @Test + public void testRoundNumbers() { + assertEquals("X", IntegerToRoman.integerToRoman(10)); + assertEquals("L", IntegerToRoman.integerToRoman(50)); + assertEquals("C", IntegerToRoman.integerToRoman(100)); + assertEquals("D", IntegerToRoman.integerToRoman(500)); + assertEquals("M", IntegerToRoman.integerToRoman(1000)); + } + + @Test + public void testEdgeCases() { + assertEquals("", IntegerToRoman.integerToRoman(0)); // Non-positive number case + assertEquals("", IntegerToRoman.integerToRoman(-5)); // Negative number case } } From 6f489e570486f77466406fca4820aec74ae513de Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Fri, 25 Oct 2024 19:50:59 +0530 Subject: [PATCH 60/75] Enhance docs, add tests in `QuickSortLinkedList` (#5998) --- .../lists/QuickSortLinkedList.java | 43 +++++++----- .../lists/QuickSortLinkedListTest.java | 66 ++++++++++++++----- 2 files changed, 76 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java index 36b6e9c62cb..08fe674b47f 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java @@ -104,36 +104,52 @@ public class QuickSortLinkedList { - private SinglyLinkedList list = null; // Linked list - private Node head = null; // head of the list - // Counstructor + private final SinglyLinkedList list; // The linked list to be sorted + private Node head; // Head of the list + + /** + * Constructor that initializes the QuickSortLinkedList with a given linked list. + * + * @param list The singly linked list to be sorted + */ public QuickSortLinkedList(SinglyLinkedList list) { this.list = list; this.head = list.getHead(); } - // Function to sort a linked list using the Quick Sort algorithm + /** + * Sorts the linked list using the QuickSort algorithm. + * The sorted list replaces the original list within the SinglyLinkedList instance. + */ public void sortList() { head = sortList(head); list.setHead(head); } - // helper function to apply QuickSort to the stored list - public Node sortList(Node head) { + + /** + * Recursively sorts a linked list by partitioning it around a pivot element. + * + *

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

+ * + * @param head The head node of the list to sort + * @return The head node of the sorted linked list + */ + private Node sortList(Node head) { if (head == null || head.next == null) { return head; } - // Choose the first element as the pivot Node pivot = head; head = head.next; pivot.next = null; - Node lessHead = new Node(); // stores the nodes cantaining data less than pivot node - Node lessTail = lessHead; // tail of lessHead - Node greaterHead = new Node(); // stores the nodes cantaining data greater than pivot node - Node greaterTail = greaterHead; // tail of greaterHead + Node lessHead = new Node(); + Node lessTail = lessHead; + Node greaterHead = new Node(); + Node greaterTail = greaterHead; - // Partition the list around the pivot while (head != null) { if (head.value < pivot.value) { lessTail.next = head; @@ -145,15 +161,12 @@ public Node sortList(Node head) { head = head.next; } - // Seperating lessHead and greaterHead to form two seperate linkedList lessTail.next = null; greaterTail.next = null; - // Recursively sort the sublists Node sortedLess = sortList(lessHead.next); Node sortedGreater = sortList(greaterHead.next); - // Combine the sorted sublists and pivot if (sortedLess == null) { pivot.next = sortedGreater; return pivot; diff --git a/src/test/java/com/thealgorithms/datastructures/lists/QuickSortLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/QuickSortLinkedListTest.java index 91e2ba5d43c..4e86f317c2e 100644 --- a/src/test/java/com/thealgorithms/datastructures/lists/QuickSortLinkedListTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/QuickSortLinkedListTest.java @@ -4,8 +4,9 @@ import static org.junit.jupiter.api.Assertions.assertNull; import org.junit.jupiter.api.Test; + /** - * Test cases for QuickSortLinkedList + * Test cases for QuickSortLinkedList. * Author: Prabhat-Kumar-42 * GitHub: https://github.com/Prabhat-Kumar-42 */ @@ -16,9 +17,8 @@ public void testSortEmptyList() { SinglyLinkedList emptyList = new SinglyLinkedList(); QuickSortLinkedList sorter = new QuickSortLinkedList(emptyList); - // Test case: Sorting an empty list should result in an empty list sorter.sortList(); - assertNull(emptyList.getHead()); + assertNull(emptyList.getHead(), "Sorted empty list should have no elements."); } @Test @@ -27,10 +27,51 @@ public void testSortSingleNodeList() { singleNodeList.insert(5); QuickSortLinkedList sorter = new QuickSortLinkedList(singleNodeList); - // Test case: Sorting a list with a single node should result in the same list sorter.sortList(); - assertEquals(5, singleNodeList.getHead().value); - assertNull(singleNodeList.getHead().next); + assertEquals(5, singleNodeList.getHead().value, "Single node list should remain unchanged after sorting."); + assertNull(singleNodeList.getHead().next, "Single node should not have a next node."); + } + + @Test + public void testSortAlreadySorted() { + SinglyLinkedList sortedList = new SinglyLinkedList(); + sortedList.insert(1); + sortedList.insert(2); + sortedList.insert(3); + sortedList.insert(4); + sortedList.insert(5); + QuickSortLinkedList sorter = new QuickSortLinkedList(sortedList); + + sorter.sortList(); + assertEquals("1->2->3->4->5", sortedList.toString(), "Already sorted list should remain unchanged."); + } + + @Test + public void testSortReverseOrderedList() { + SinglyLinkedList reverseList = new SinglyLinkedList(); + reverseList.insert(5); + reverseList.insert(4); + reverseList.insert(3); + reverseList.insert(2); + reverseList.insert(1); + QuickSortLinkedList sorter = new QuickSortLinkedList(reverseList); + + sorter.sortList(); + assertEquals("1->2->3->4->5", reverseList.toString(), "Reverse ordered list should be sorted in ascending order."); + } + + @Test + public void testSortWithDuplicates() { + SinglyLinkedList listWithDuplicates = new SinglyLinkedList(); + listWithDuplicates.insert(3); + listWithDuplicates.insert(1); + listWithDuplicates.insert(3); + listWithDuplicates.insert(2); + listWithDuplicates.insert(2); + QuickSortLinkedList sorter = new QuickSortLinkedList(listWithDuplicates); + + sorter.sortList(); + assertEquals("1->2->2->3->3", listWithDuplicates.toString(), "List with duplicates should be sorted correctly."); } @Test @@ -48,18 +89,7 @@ public void testSortMultipleElementsList() { list.insert(6); QuickSortLinkedList sorter = new QuickSortLinkedList(list); - // Test case: Sorting a list with multiple elements sorter.sortList(); - assertEquals(1, list.getHead().value); - assertEquals(2, list.getHead().next.value); - assertEquals(3, list.getHead().next.next.value); - assertEquals(4, list.getHead().next.next.next.value); - assertEquals(5, list.getHead().next.next.next.next.value); - assertEquals(6, list.getHead().next.next.next.next.next.value); - assertEquals(7, list.getHead().next.next.next.next.next.next.value); - assertEquals(8, list.getHead().next.next.next.next.next.next.next.value); - assertEquals(9, list.getHead().next.next.next.next.next.next.next.next.value); - assertEquals(10, list.getHead().next.next.next.next.next.next.next.next.next.value); - assertNull(list.getHead().next.next.next.next.next.next.next.next.next.next); + assertEquals("1->2->3->4->5->6->7->8->9->10", list.toString(), "List should be sorted in ascending order."); } } From fed2d4b5efb057b1f35135651d290bfcea9bc3e4 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Fri, 25 Oct 2024 19:55:55 +0530 Subject: [PATCH 61/75] =?UTF-8?q?Enhance=20docs,=20remove=20`main`,=20add?= =?UTF-8?q?=20tests=20in=20`MergeSortedSingl=E2=80=A6=20(#5997)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DIRECTORY.md | 1 + .../lists/MergeSortedSinglyLinkedList.java | 64 ++++++++----- .../MergeSortedSinglyLinkedListTest.java | 90 +++++++++++++++++++ 3 files changed, 131 insertions(+), 24 deletions(-) create mode 100644 src/test/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedListTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 1def3e25c06..4ceaefd6ea0 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -830,6 +830,7 @@ * lists * [CircleLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CircleLinkedListTest.java) * [CreateAndDetectLoopTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoopTest.java) + * [MergeSortedSinglyLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedListTest.java) * [QuickSortLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/QuickSortLinkedListTest.java) * [ReverseKGroupTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/ReverseKGroupTest.java) * [RotateSinglyLinkedListsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedListsTest.java) diff --git a/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedList.java index 2bee945c9db..a16b202c450 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedList.java @@ -1,31 +1,49 @@ package com.thealgorithms.datastructures.lists; +/** + * Utility class for merging two sorted singly linked lists. + * + *

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

+ * + *

Example usage:

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

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

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

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

* - * @param listA the first sorted list - * @param listB the second sored list - * @return merged sorted list + * @param listA The first sorted singly linked list. + * @param listB The second sorted singly linked list. + * @return A new singly linked list containing all elements from both lists in sorted order. + * @throws NullPointerException if either input list is null. */ public static SinglyLinkedList merge(SinglyLinkedList listA, SinglyLinkedList listB) { + if (listA == null || listB == null) { + throw new NullPointerException("Input lists must not be null."); + } + Node headA = listA.getHead(); Node headB = listB.getHead(); - int size = listA.size() + listB.size(); Node head = new Node(); @@ -40,12 +58,10 @@ public static SinglyLinkedList merge(SinglyLinkedList listA, SinglyLinkedList li } tail = tail.next; } - if (headA == null) { - tail.next = headB; - } - if (headB == null) { - tail.next = headA; - } + + // Attach remaining nodes + tail.next = (headA == null) ? headB : headA; + return new SinglyLinkedList(head.next, size); } } diff --git a/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedListTest.java new file mode 100644 index 00000000000..b6b785f4371 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedListTest.java @@ -0,0 +1,90 @@ +package com.thealgorithms.datastructures.lists; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class MergeSortedSinglyLinkedListTest { + + @Test + void testMergeTwoSortedLists() { + SinglyLinkedList listA = new SinglyLinkedList(); + SinglyLinkedList listB = new SinglyLinkedList(); + + for (int i = 2; i <= 10; i += 2) { + listA.insert(i); + listB.insert(i - 1); + } + + SinglyLinkedList mergedList = MergeSortedSinglyLinkedList.merge(listA, listB); + assertEquals("1->2->3->4->5->6->7->8->9->10", mergedList.toString(), "Merged list should contain all elements in sorted order."); + } + + @Test + void testMergeWithEmptyListA() { + SinglyLinkedList listA = new SinglyLinkedList(); // Empty listA + SinglyLinkedList listB = new SinglyLinkedList(); + listB.insert(1); + listB.insert(3); + listB.insert(5); + + SinglyLinkedList mergedList = MergeSortedSinglyLinkedList.merge(listA, listB); + assertEquals("1->3->5", mergedList.toString(), "Merged list should match listB when listA is empty."); + } + + @Test + void testMergeWithEmptyListB() { + SinglyLinkedList listA = new SinglyLinkedList(); + SinglyLinkedList listB = new SinglyLinkedList(); // Empty listB + listA.insert(2); + listA.insert(4); + listA.insert(6); + + SinglyLinkedList mergedList = MergeSortedSinglyLinkedList.merge(listA, listB); + assertEquals("2->4->6", mergedList.toString(), "Merged list should match listA when listB is empty."); + } + + @Test + void testMergeWithBothEmptyLists() { + SinglyLinkedList listA = new SinglyLinkedList(); // Empty listA + SinglyLinkedList listB = new SinglyLinkedList(); // Empty listB + + SinglyLinkedList mergedList = MergeSortedSinglyLinkedList.merge(listA, listB); + assertEquals("", mergedList.toString(), "Merged list should be empty when both input lists are empty."); + } + + @Test + void testMergeWithDuplicateValues() { + SinglyLinkedList listA = new SinglyLinkedList(); + SinglyLinkedList listB = new SinglyLinkedList(); + + listA.insert(1); + listA.insert(3); + listA.insert(5); + listB.insert(1); + listB.insert(4); + listB.insert(5); + + SinglyLinkedList mergedList = MergeSortedSinglyLinkedList.merge(listA, listB); + assertEquals("1->1->3->4->5->5", mergedList.toString(), "Merged list should include duplicate values in sorted order."); + } + + @Test + void testMergeThrowsExceptionOnNullInput() { + SinglyLinkedList listA = null; + SinglyLinkedList listB = new SinglyLinkedList(); + listB.insert(1); + listB.insert(2); + + SinglyLinkedList finalListA = listA; + SinglyLinkedList finalListB = listB; + assertThrows(NullPointerException.class, () -> MergeSortedSinglyLinkedList.merge(finalListA, finalListB), "Should throw NullPointerException if listA is null."); + + listA = new SinglyLinkedList(); + listB = null; + SinglyLinkedList finalListA1 = listA; + SinglyLinkedList finalListB1 = listB; + assertThrows(NullPointerException.class, () -> MergeSortedSinglyLinkedList.merge(finalListA1, finalListB1), "Should throw NullPointerException if listB is null."); + } +} From c8e9e748b2bcb2e7fd430cd207dabdda39fa1800 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Fri, 25 Oct 2024 20:03:40 +0530 Subject: [PATCH 62/75] =?UTF-8?q?Enhance=20docs,=20remove=20`main`,=20add?= =?UTF-8?q?=20tests=20in=20`MergeSortedArray=E2=80=A6=20(#5996)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DIRECTORY.md | 1 + .../lists/MergeSortedArrayList.java | 67 +++++++------ .../lists/MergeSortedArrayListTest.java | 99 +++++++++++++++++++ 3 files changed, 136 insertions(+), 31 deletions(-) create mode 100644 src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 4ceaefd6ea0..95805ff411e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -830,6 +830,7 @@ * lists * [CircleLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CircleLinkedListTest.java) * [CreateAndDetectLoopTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoopTest.java) + * [MergeSortedArrayListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java) * [MergeSortedSinglyLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedListTest.java) * [QuickSortLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/QuickSortLinkedListTest.java) * [ReverseKGroupTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/ReverseKGroupTest.java) diff --git a/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedArrayList.java b/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedArrayList.java index e315c423633..09eb854c8dc 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedArrayList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedArrayList.java @@ -1,49 +1,55 @@ package com.thealgorithms.datastructures.lists; -import java.util.ArrayList; import java.util.Collection; import java.util.List; /** + * Utility class for merging two sorted ArrayLists of integers into a single sorted collection. + * + *

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

+ * + *

Example usage:

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

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

+ * + *

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

+ * + *

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

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

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

* - * @param listA the first list to merge - * @param listB the second list to merge - * @param listC the result list after merging + * @param listA The first sorted list of integers. + * @param listB The second sorted list of integers. + * @param listC The collection to hold the merged result, maintaining sorted order. + * @throws NullPointerException if any of the input lists or result collection is null. */ public static void merge(List listA, List listB, Collection listC) { + if (listA == null || listB == null || listC == null) { + throw new NullPointerException("Input lists and result collection must not be null."); + } + int pa = 0; - /* the index of listA */ int pb = 0; - /* the index of listB */ while (pa < listA.size() && pb < listB.size()) { if (listA.get(pa) <= listB.get(pb)) { @@ -53,12 +59,11 @@ public static void merge(List listA, List listB, Collection listA = Arrays.asList(1, 3, 5, 7, 9); + List listB = Arrays.asList(2, 4, 6, 8, 10); + List result = new ArrayList<>(); + + MergeSortedArrayList.merge(listA, listB, result); + + List expected = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + assertEquals(expected, result, "Merged list should be sorted and contain all elements from both input lists."); + } + + @Test + void testMergeWithEmptyList() { + List listA = Arrays.asList(1, 2, 3); + List listB = new ArrayList<>(); // Empty list + List result = new ArrayList<>(); + + MergeSortedArrayList.merge(listA, listB, result); + + List expected = Arrays.asList(1, 2, 3); + assertEquals(expected, result, "Merged list should match listA when listB is empty."); + } + + @Test + void testMergeWithBothEmptyLists() { + List listA = new ArrayList<>(); // Empty list + List listB = new ArrayList<>(); // Empty list + List result = new ArrayList<>(); + + MergeSortedArrayList.merge(listA, listB, result); + + assertTrue(result.isEmpty(), "Merged list should be empty when both input lists are empty."); + } + + @Test + void testMergeWithDuplicateElements() { + List listA = Arrays.asList(1, 2, 2, 3); + List listB = Arrays.asList(2, 3, 4); + List result = new ArrayList<>(); + + MergeSortedArrayList.merge(listA, listB, result); + + List expected = Arrays.asList(1, 2, 2, 2, 3, 3, 4); + assertEquals(expected, result, "Merged list should correctly handle and include duplicate elements."); + } + + @Test + void testMergeWithNegativeAndPositiveNumbers() { + List listA = Arrays.asList(-3, -1, 2); + List listB = Arrays.asList(-2, 0, 3); + List result = new ArrayList<>(); + + MergeSortedArrayList.merge(listA, listB, result); + + List expected = Arrays.asList(-3, -2, -1, 0, 2, 3); + assertEquals(expected, result, "Merged list should correctly handle negative and positive numbers."); + } + + @Test + void testMergeThrowsExceptionOnNullInput() { + List listA = null; + List listB = Arrays.asList(1, 2, 3); + List result = new ArrayList<>(); + + List finalListB = listB; + List finalListA = listA; + List finalResult = result; + assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(finalListA, finalListB, finalResult), "Should throw NullPointerException if any input list is null."); + + listA = Arrays.asList(1, 2, 3); + listB = null; + List finalListA1 = listA; + List finalListB1 = listB; + List finalResult1 = result; + assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(finalListA1, finalListB1, finalResult1), "Should throw NullPointerException if any input list is null."); + + listA = Arrays.asList(1, 2, 3); + listB = Arrays.asList(4, 5, 6); + result = null; + List finalListA2 = listA; + List finalListB2 = listB; + List finalResult2 = result; + assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(finalListA2, finalListB2, finalResult2), "Should throw NullPointerException if the result collection is null."); + } +} From cd87dc38776da2721bbac71a969166573685e574 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Fri, 25 Oct 2024 20:09:17 +0530 Subject: [PATCH 63/75] Enhance docs, add tests in `CreateAndDetectLoop` (#5993) --- .../lists/CreateAndDetectLoop.java | 53 ++++++++++++------- .../lists/CreateAndDetectLoopTest.java | 21 ++++++-- 2 files changed, 51 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoop.java b/src/main/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoop.java index 49115b2d0f3..3902d08bfd1 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoop.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoop.java @@ -1,11 +1,24 @@ package com.thealgorithms.datastructures.lists; +/** + * CreateAndDetectLoop provides utility methods for creating and detecting loops + * (cycles) in a singly linked list. Loops in a linked list are created by + * connecting the "next" pointer of one node to a previous node in the list, + * forming a cycle. + */ public final class CreateAndDetectLoop { - // Node class representing a single node in the linked list + /** + * Private constructor to prevent instantiation of this utility class. + */ private CreateAndDetectLoop() { throw new UnsupportedOperationException("Utility class"); } + + /** + * Node represents an individual element in the linked list, containing + * data and a reference to the next node. + */ static final class Node { int data; Node next; @@ -16,19 +29,16 @@ static final class Node { } } - // Method to create a loop between two specific positions in the linked list - /* - * Test case that shows the Cycle(loop) in a LinkedList - * Let's take this linked list: - * 1->2->3->4->5->6 - * \______/ - * In this linked list we can see there is a cycle. - * we can create loop by calling createLoop function in main after creating LL - * createLoop(head,2,5); - * to detect there is loop or not we can call detectloop function in main - * detectloop(head); + /** + * Creates a loop in a linked list by connecting the next pointer of a node + * at a specified starting position (position2) to another node at a specified + * destination position (position1). If either position is invalid, no loop + * will be created. + * + * @param head the head node of the linked list + * @param position1 the position in the list where the loop should end + * @param position2 the position in the list where the loop should start */ - static void createLoop(Node head, int position1, int position2) { if (position1 == 0 || position2 == 0) { return; @@ -39,29 +49,32 @@ static void createLoop(Node head, int position1, int position2) { int count1 = 1; int count2 = 1; - // Traverse to find node at position1 + // Traverse to the node at position1 while (count1 < position1 && node1 != null) { node1 = node1.next; count1++; } - // Traverse to find node at position2 + // Traverse to the node at position2 while (count2 < position2 && node2 != null) { node2 = node2.next; count2++; } - // Create a loop by connecting node2's next to node1 + // If both nodes are valid, create the loop if (node1 != null && node2 != null) { node2.next = node1; } } - // Method to detect a loop in the linked list + /** - * Detects the presence of a loop in the linked list. + * Detects the presence of a loop in the linked list using Floyd's cycle-finding + * algorithm, also known as the "tortoise and hare" method. * - * @see Floyd's Cycle Detection Algorithm - * @return true if loop exists else false + * @param head the head node of the linked list + * @return true if a loop is detected, false otherwise + * @see + * Floyd's Cycle Detection Algorithm */ static boolean detectLoop(Node head) { Node sptr = head; diff --git a/src/test/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoopTest.java b/src/test/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoopTest.java index 5e9d4c3a291..2e1012f2b79 100644 --- a/src/test/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoopTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoopTest.java @@ -12,7 +12,7 @@ public class CreateAndDetectLoopTest { @BeforeEach void setUp() { - // Create a linked list: 1 -> 2 -> 3 -> 4 -> 5 -> 6 + // Set up a linked list: 1 -> 2 -> 3 -> 4 -> 5 -> 6 head = new CreateAndDetectLoop.Node(1); CreateAndDetectLoop.Node second = new CreateAndDetectLoop.Node(2); CreateAndDetectLoop.Node third = new CreateAndDetectLoop.Node(3); @@ -44,7 +44,7 @@ void testCreateAndDetectLoopLoopExists() { @Test void testCreateLoopInvalidPosition() { - // Create loop with invalid positions + // Create loop with invalid positions (0) CreateAndDetectLoop.createLoop(head, 0, 0); // Ensure no loop was created @@ -62,10 +62,25 @@ void testCreateLoopSelfLoop() { @Test void testCreateLoopNoChangeForNonExistentPositions() { - // Create a loop with positions that don't exist in the linked list + // Create a loop with non-existent positions CreateAndDetectLoop.createLoop(head, 10, 20); // Ensure no loop was created assertFalse(CreateAndDetectLoop.detectLoop(head), "No loop should be created if positions are out of bounds."); } + + @Test + void testMultipleNodesWithNoLoop() { + // Multiple nodes without creating any loop + assertFalse(CreateAndDetectLoop.detectLoop(head), "No loop should be detected for a standard linear list."); + } + + @Test + void testHeadToTailLoop() { + // Create a loop from the tail back to the head + CreateAndDetectLoop.createLoop(head, 1, 6); + + // Detect the head-to-tail loop + assertTrue(CreateAndDetectLoop.detectLoop(head), "A head-to-tail loop should be detected."); + } } From 3a8b04afeabd99de6e927edde4821d01937b2c3b Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Fri, 25 Oct 2024 20:19:55 +0530 Subject: [PATCH 64/75] Enhance docs, add tests in MergeKSortedLinkedList (#5995) --- DIRECTORY.md | 1 + .../lists/MergeKSortedLinkedList.java | 85 ++++++++++++----- .../lists/MergeKSortedLinkedListTest.java | 93 +++++++++++++++++++ 3 files changed, 158 insertions(+), 21 deletions(-) create mode 100644 src/test/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedListTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 95805ff411e..820b80cde7e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -830,6 +830,7 @@ * lists * [CircleLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CircleLinkedListTest.java) * [CreateAndDetectLoopTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoopTest.java) + * [MergeKSortedLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedListTest.java) * [MergeSortedArrayListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java) * [MergeSortedSinglyLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedListTest.java) * [QuickSortLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/QuickSortLinkedListTest.java) diff --git a/src/main/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedList.java index 0eac20d2e9a..0f5c50530d9 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedList.java @@ -1,51 +1,94 @@ package com.thealgorithms.datastructures.lists; -import java.util.Arrays; import java.util.Comparator; import java.util.PriorityQueue; /** + * The MergeKSortedLinkedList class provides a method to merge multiple sorted linked lists + * into a single sorted linked list. + * This implementation uses a min-heap (priority queue) to efficiently + * find the smallest node across all lists, thus optimizing the merge process. + * + *

Example usage: + *

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

+ * + *

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

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

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

+ * + * @param a Array of linked list heads to be merged. + * @param n Number of linked lists. + * @return Head of the merged sorted linked list. */ Node mergeKList(Node[] a, int n) { - // Min Heap - PriorityQueue min = new PriorityQueue<>(Comparator.comparingInt(x -> x.data)); + if (a == null || n == 0) { + return null; + } - // adding head of all linkedList in min heap - min.addAll(Arrays.asList(a).subList(0, n)); + // Min Heap to store nodes based on their values for efficient retrieval of the smallest element. + PriorityQueue minHeap = new PriorityQueue<>(Comparator.comparingInt(x -> x.data)); - // Make new head among smallest heads in K linkedList - Node head = min.poll(); - min.add(head.next); - Node curr = head; + // Initialize the min-heap with the head of each non-null linked list + for (Node node : a) { + if (node != null) { + minHeap.add(node); + } + } + + // Start merging process + Node head = minHeap.poll(); // Smallest head is the initial head of the merged list + if (head != null && head.next != null) { + minHeap.add(head.next); + } - // merging LinkedList - while (!min.isEmpty()) { - Node temp = min.poll(); + Node curr = head; + while (!minHeap.isEmpty()) { + Node temp = minHeap.poll(); curr.next = temp; curr = temp; - // Add Node in min Heap only if temp.next is not null + // Add the next node in the current list to the heap if it exists if (temp.next != null) { - min.add(temp.next); + minHeap.add(temp.next); } } return head; } - private final class Node { + /** + * Represents a node in the linked list. + */ + static class Node { + int data; + Node next; + + Node(int data) { + this.data = data; + this.next = null; + } - private int data; - private Node next; + Node(int data, Node next) { + this.data = data; + this.next = next; + } } } diff --git a/src/test/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedListTest.java new file mode 100644 index 00000000000..99a890112d3 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedListTest.java @@ -0,0 +1,93 @@ +package com.thealgorithms.datastructures.lists; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import com.thealgorithms.datastructures.lists.MergeKSortedLinkedList.Node; +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +class MergeKSortedLinkedListTest { + + @Test + void testMergeKLists() { + Node list1 = new Node(1, new Node(4, new Node(5))); + Node list2 = new Node(1, new Node(3, new Node(4))); + Node list3 = new Node(2, new Node(6)); + Node[] lists = {list1, list2, list3}; + + MergeKSortedLinkedList merger = new MergeKSortedLinkedList(); + Node mergedHead = merger.mergeKList(lists, lists.length); + + int[] expectedValues = {1, 1, 2, 3, 4, 4, 5, 6}; + int[] actualValues = getListValues(mergedHead); + assertArrayEquals(expectedValues, actualValues, "Merged list values do not match the expected sorted order."); + } + + @Test + void testMergeEmptyLists() { + Node[] lists = {null, null, null}; + + MergeKSortedLinkedList merger = new MergeKSortedLinkedList(); + Node mergedHead = merger.mergeKList(lists, lists.length); + + assertNull(mergedHead, "Merged list should be null when all input lists are empty."); + } + + @Test + void testMergeSingleList() { + Node list1 = new Node(1, new Node(3, new Node(5))); + Node[] lists = {list1}; + + MergeKSortedLinkedList merger = new MergeKSortedLinkedList(); + Node mergedHead = merger.mergeKList(lists, lists.length); + + int[] expectedValues = {1, 3, 5}; + int[] actualValues = getListValues(mergedHead); + assertArrayEquals(expectedValues, actualValues, "Merged list should match the single input list when only one list is provided."); + } + + @Test + void testMergeListsOfDifferentLengths() { + Node list1 = new Node(1, new Node(3, new Node(5))); + Node list2 = new Node(2, new Node(4)); + Node list3 = new Node(6); + Node[] lists = {list1, list2, list3}; + + MergeKSortedLinkedList merger = new MergeKSortedLinkedList(); + Node mergedHead = merger.mergeKList(lists, lists.length); + + int[] expectedValues = {1, 2, 3, 4, 5, 6}; + int[] actualValues = getListValues(mergedHead); + assertArrayEquals(expectedValues, actualValues, "Merged list values do not match expected sorted order for lists of different lengths."); + } + + @Test + void testMergeSingleElementLists() { + Node list1 = new Node(1); + Node list2 = new Node(3); + Node list3 = new Node(2); + Node[] lists = {list1, list2, list3}; + + MergeKSortedLinkedList merger = new MergeKSortedLinkedList(); + Node mergedHead = merger.mergeKList(lists, lists.length); + + int[] expectedValues = {1, 2, 3}; + int[] actualValues = getListValues(mergedHead); + assertArrayEquals(expectedValues, actualValues, "Merged list values do not match expected sorted order for single-element lists."); + } + + /** + * Helper method to extract values from the linked list into an array for assertion. + */ + private int[] getListValues(Node head) { + int[] values = new int[100]; // assuming max length for simplicity + int i = 0; + Node curr = head; + while (curr != null) { + values[i++] = curr.data; + curr = curr.next; + } + return Arrays.copyOf(values, i); // return only filled part + } +} From f3e0900d2b86aecf69b2c519e1defac6f8e5181c Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Fri, 25 Oct 2024 20:31:06 +0530 Subject: [PATCH 65/75] Enhance docs, add tests in `CursorLinkedList` (#5994) --- DIRECTORY.md | 1 + .../lists/CursorLinkedList.java | 142 ++++++++++-------- .../lists/CursorLinkedListTest.java | 112 ++++++++++++++ 3 files changed, 196 insertions(+), 59 deletions(-) create mode 100644 src/test/java/com/thealgorithms/datastructures/lists/CursorLinkedListTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 820b80cde7e..6a733019a67 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -830,6 +830,7 @@ * lists * [CircleLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CircleLinkedListTest.java) * [CreateAndDetectLoopTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoopTest.java) + * [CursorLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CursorLinkedListTest.java) * [MergeKSortedLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedListTest.java) * [MergeSortedArrayListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java) * [MergeSortedSinglyLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedListTest.java) diff --git a/src/main/java/com/thealgorithms/datastructures/lists/CursorLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/CursorLinkedList.java index b4fa9c51d4d..ff3d39115c3 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/CursorLinkedList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/CursorLinkedList.java @@ -3,18 +3,20 @@ import java.util.Objects; /** - * This class implements a Cursor Linked List. - * - * A CursorLinkedList is an array version of a Linked List. Essentially you have - * an array of list nodes but instead of each node containing a pointer to the - * next item in the linked list, each node element in the array contains the - * index for the next node element. + * CursorLinkedList is an array-based implementation of a singly linked list. + * Each node in the array simulates a linked list node, storing an element and + * the index of the next node. This structure allows for efficient list operations + * without relying on traditional pointers. * + * @param the type of elements in this list */ public class CursorLinkedList { + /** + * Node represents an individual element in the list, containing the element + * itself and a pointer (index) to the next node. + */ private static class Node { - T element; int next; @@ -31,7 +33,7 @@ private static class Node { private static final int CURSOR_SPACE_SIZE = 100; { - // init at loading time + // Initialize cursor space array and free list pointers cursorSpace = new Node[CURSOR_SPACE_SIZE]; for (int i = 0; i < CURSOR_SPACE_SIZE; i++) { cursorSpace[i] = new Node<>(null, i + 1); @@ -39,12 +41,18 @@ private static class Node { cursorSpace[CURSOR_SPACE_SIZE - 1].next = 0; } + /** + * Constructs an empty CursorLinkedList with the default capacity. + */ public CursorLinkedList() { os = 0; count = 0; head = -1; } + /** + * Prints all elements in the list in their current order. + */ public void printList() { if (head != -1) { int start = head; @@ -57,27 +65,36 @@ public void printList() { } /** - * @return the logical index of the element within the list , not the actual - * index of the [cursorSpace] array + * Finds the logical index of a specified element in the list. + * + * @param element the element to search for in the list + * @return the logical index of the element, or -1 if not found + * @throws NullPointerException if element is null */ public int indexOf(T element) { - Objects.requireNonNull(element); - Node iterator = cursorSpace[head]; - for (int i = 0; i < count; i++) { - if (iterator.element.equals(element)) { - return i; + if (element == null) { + throw new NullPointerException("Element cannot be null"); + } + try { + Objects.requireNonNull(element); + Node iterator = cursorSpace[head]; + for (int i = 0; i < count; i++) { + if (iterator.element.equals(element)) { + return i; + } + iterator = cursorSpace[iterator.next]; } - iterator = cursorSpace[iterator.next]; + } catch (Exception e) { + return -1; } - return -1; } /** - * @param position , the logical index of the element , not the actual one - * within the [cursorSpace] array . this method should be used to get the - * index give by indexOf() method. - * @return + * Retrieves an element at a specified logical index in the list. + * + * @param position the logical index of the element + * @return the element at the specified position, or null if index is out of bounds */ public T get(int position) { if (position >= 0 && position < count) { @@ -88,15 +105,18 @@ public T get(int position) { if (counter == position) { return element; } - start = cursorSpace[start].next; counter++; } } - return null; } + /** + * Removes the element at a specified logical index from the list. + * + * @param index the logical index of the element to remove + */ public void removeByIndex(int index) { if (index >= 0 && index < count) { T element = get(index); @@ -104,19 +124,22 @@ public void removeByIndex(int index) { } } + /** + * Removes a specified element from the list. + * + * @param element the element to be removed + * @throws NullPointerException if element is null + */ public void remove(T element) { Objects.requireNonNull(element); - - // case element is in the head T tempElement = cursorSpace[head].element; int tempNext = cursorSpace[head].next; if (tempElement.equals(element)) { free(head); head = tempNext; - } else { // otherwise cases + } else { int prevIndex = head; int currentIndex = cursorSpace[prevIndex].next; - while (currentIndex != -1) { T currentElement = cursorSpace[currentIndex].element; if (currentElement.equals(element)) { @@ -124,15 +147,34 @@ public void remove(T element) { free(currentIndex); break; } - prevIndex = currentIndex; currentIndex = cursorSpace[prevIndex].next; } } - count--; } + /** + * Allocates a new node index for storing an element. + * + * @return the index of the newly allocated node + * @throws OutOfMemoryError if no space is available in cursor space + */ + private int alloc() { + int availableNodeIndex = cursorSpace[os].next; + if (availableNodeIndex == 0) { + throw new OutOfMemoryError(); + } + cursorSpace[os].next = cursorSpace[availableNodeIndex].next; + cursorSpace[availableNodeIndex].next = -1; + return availableNodeIndex; + } + + /** + * Releases a node back to the free list. + * + * @param index the index of the node to release + */ private void free(int index) { Node osNode = cursorSpace[os]; int osNext = osNode.next; @@ -141,44 +183,26 @@ private void free(int index) { cursorSpace[index].next = osNext; } + /** + * Appends an element to the end of the list. + * + * @param element the element to append + * @throws NullPointerException if element is null + */ public void append(T element) { Objects.requireNonNull(element); int availableIndex = alloc(); cursorSpace[availableIndex].element = element; - if (head == -1) { head = availableIndex; + } else { + int iterator = head; + while (cursorSpace[iterator].next != -1) { + iterator = cursorSpace[iterator].next; + } + cursorSpace[iterator].next = availableIndex; } - - int iterator = head; - while (cursorSpace[iterator].next != -1) { - iterator = cursorSpace[iterator].next; - } - - cursorSpace[iterator].next = availableIndex; cursorSpace[availableIndex].next = -1; - count++; } - - /** - * @return the index of the next available node - */ - private int alloc() { - // 1- get the index at which the os is pointing - int availableNodeIndex = cursorSpace[os].next; - - if (availableNodeIndex == 0) { - throw new OutOfMemoryError(); - } - - // 2- make the os point to the next of the @var{availableNodeIndex} - cursorSpace[os].next = cursorSpace[availableNodeIndex].next; - - // this to indicate an end of the list , helpful at testing since any err - // would throw an outOfBoundException - cursorSpace[availableNodeIndex].next = -1; - - return availableNodeIndex; - } } diff --git a/src/test/java/com/thealgorithms/datastructures/lists/CursorLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/CursorLinkedListTest.java new file mode 100644 index 00000000000..bf550182699 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/lists/CursorLinkedListTest.java @@ -0,0 +1,112 @@ +package com.thealgorithms.datastructures.lists; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class CursorLinkedListTest { + private CursorLinkedList list; + + @BeforeEach + void setUp() { + list = new CursorLinkedList<>(); + } + + @Test + void testAppendAndGet() { + list.append("First"); + list.append("Second"); + list.append("Third"); + + assertEquals("First", list.get(0)); + assertEquals("Second", list.get(1)); + assertEquals("Third", list.get(2)); + assertNull(list.get(3)); + assertNull(list.get(-1)); + } + + @Test + void testIndexOf() { + list.append("First"); + list.append("Second"); + list.append("Third"); + + assertEquals(0, list.indexOf("First")); + assertEquals(1, list.indexOf("Second")); + assertEquals(2, list.indexOf("Third")); + assertEquals(-1, list.indexOf("NonExistent")); + } + + @Test + void testRemove() { + list.append("First"); + list.append("Second"); + list.append("Third"); + + list.remove("Second"); + assertEquals("First", list.get(0)); + assertEquals("Third", list.get(1)); + assertNull(list.get(2)); + assertEquals(-1, list.indexOf("Second")); + } + + @Test + void testRemoveByIndex() { + list.append("First"); + list.append("Second"); + list.append("Third"); + + list.removeByIndex(1); + assertEquals("First", list.get(0)); + assertEquals("Third", list.get(1)); + assertNull(list.get(2)); + } + + @Test + void testRemoveFirstElement() { + list.append("First"); + list.append("Second"); + + list.remove("First"); + assertEquals("Second", list.get(0)); + assertNull(list.get(1)); + assertEquals(-1, list.indexOf("First")); + } + + @Test + void testRemoveLastElement() { + list.append("First"); + list.append("Second"); + + list.remove("Second"); + assertEquals("First", list.get(0)); + assertNull(list.get(1)); + assertEquals(-1, list.indexOf("Second")); + } + + @Test + void testNullHandling() { + assertThrows(NullPointerException.class, () -> list.append(null)); + assertThrows(NullPointerException.class, () -> list.remove(null)); + assertThrows(NullPointerException.class, () -> list.indexOf(null)); + } + + @Test + void testEmptyList() { + assertNull(list.get(0)); + assertEquals(-1, list.indexOf("Any")); + } + + @Test + void testMemoryLimitExceeded() { + // Test adding more elements than CURSOR_SPACE_SIZE + assertThrows(OutOfMemoryError.class, () -> { + for (int i = 0; i < 101; i++) { // CURSOR_SPACE_SIZE is 100 + list.append("Element" + i); + } + }); + } +} From ed35374ab04fef115d198ef407322115633f27d0 Mon Sep 17 00:00:00 2001 From: jasonjyu Date: Fri, 25 Oct 2024 11:05:43 -0400 Subject: [PATCH 66/75] Add to WordLadderTest corner case where wordList is empty (#5908) --- .../com/thealgorithms/strings/WordLadderTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/test/java/com/thealgorithms/strings/WordLadderTest.java b/src/test/java/com/thealgorithms/strings/WordLadderTest.java index d933ebeddc5..0854ad2b0c1 100644 --- a/src/test/java/com/thealgorithms/strings/WordLadderTest.java +++ b/src/test/java/com/thealgorithms/strings/WordLadderTest.java @@ -41,6 +41,21 @@ public void testWordLadder2() { assertEquals(WordLadder.ladderLength("hit", "cog", wordList2), 0); } + /** + * Test 3: + * Input: beginWord = "hit", endWord = "cog", wordList = + * [] + * Output: 0 + * Explanation: The wordList is empty (corner case), + * therefore there is no valid transformation sequence. + */ + @Test + public void testWordLadder3() { + + List wordList3 = Arrays.asList(); + assertEquals(WordLadder.ladderLength("hit", "cog", wordList3), 0); + } + @ParameterizedTest @CsvSource({"'a', 'c', 'b,c', 2", "'a', 'c', 'a', 0", "'a', 'a', 'a', 0", "'ab', 'cd', 'ad,bd,cd', 3", "'a', 'd', 'b,c,d', 2", "'a', 'd', 'b,c,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,d', 2"}) void testLadderLength(String beginWord, String endWord, String wordListStr, int expectedLength) { From c766c5e812a4940e6357a49c9a23aa95c8cd5a4b Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Fri, 25 Oct 2024 20:40:15 +0530 Subject: [PATCH 67/75] =?UTF-8?q?Enhance=20docs,=20remove=20`main`,=20add?= =?UTF-8?q?=20tests=20in=20`CountSinglyLinke=E2=80=A6=20(#5992)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DIRECTORY.md | 1 + .../lists/CountSinglyLinkedListRecursion.java | 23 +++++---- .../CountSinglyLinkedListRecursionTest.java | 49 +++++++++++++++++++ 3 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 6a733019a67..744e16db8d6 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -829,6 +829,7 @@ * [LeftistHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/LeftistHeapTest.java) * lists * [CircleLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CircleLinkedListTest.java) + * [CountSinglyLinkedListRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java) * [CreateAndDetectLoopTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoopTest.java) * [CursorLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CursorLinkedListTest.java) * [MergeKSortedLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedListTest.java) diff --git a/src/main/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursion.java b/src/main/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursion.java index 8d864bc8caa..4c1ffe9d3ea 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursion.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursion.java @@ -1,27 +1,26 @@ package com.thealgorithms.datastructures.lists; +/** + * CountSinglyLinkedListRecursion extends a singly linked list to include a + * recursive count method, which calculates the number of nodes in the list. + */ public class CountSinglyLinkedListRecursion extends SinglyLinkedList { - public static void main(String[] args) { - CountSinglyLinkedListRecursion list = new CountSinglyLinkedListRecursion(); - for (int i = 1; i <= 5; ++i) { - list.insert(i); - } - assert list.count() == 5; - } - /** - * Calculate the count of the list manually using recursion. + * Recursively calculates the number of nodes in the list. * - * @param head head of the list. - * @return count of the list. + * @param head the head node of the list segment being counted. + * @return the count of nodes from the given head node onward. */ private int countRecursion(Node head) { return head == null ? 0 : 1 + countRecursion(head.next); } /** - * Returns the count of the list. + * Returns the total number of nodes in the list by invoking the recursive + * count helper method. + * + * @return the total node count in the list. */ @Override public int count() { diff --git a/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java b/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java new file mode 100644 index 00000000000..1a3efe8a557 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java @@ -0,0 +1,49 @@ +package com.thealgorithms.datastructures.lists; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class CountSinglyLinkedListRecursionTest { + + private CountSinglyLinkedListRecursion list; + + @BeforeEach + public void setUp() { + list = new CountSinglyLinkedListRecursion(); + } + + @Test + public void testCountEmptyList() { + // An empty list should have a count of 0 + assertEquals(0, list.count(), "Count of an empty list should be 0."); + } + + @Test + public void testCountSingleElementList() { + // Insert a single element and check the count + list.insert(1); + assertEquals(1, list.count(), "Count of a single-element list should be 1."); + } + + @Test + public void testCountMultipleElements() { + // Insert multiple elements and check the count + for (int i = 1; i <= 5; i++) { + list.insert(i); + } + assertEquals(5, list.count(), "Count of a list with 5 elements should be 5."); + } + + @Test + public void testCountWithDuplicateElements() { + // Insert duplicate elements and verify the count is correct + list.insert(1); + list.insert(2); + list.insert(2); + list.insert(3); + list.insert(3); + assertEquals(5, list.count(), "Count of a list with duplicate elements should match total node count."); + } +} From 202879aa58dddf56c8dee9e53eeb178caaf620fb Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Fri, 25 Oct 2024 20:46:34 +0530 Subject: [PATCH 68/75] Enhance docs, add tests in `CircleLinkedList` (#5991) --- .../lists/CircleLinkedList.java | 79 ++++++++++++------ .../lists/CircleLinkedListTest.java | 81 ++++++++++++++----- 2 files changed, 114 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/lists/CircleLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/CircleLinkedList.java index 2b50f73101f..422e8953625 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/CircleLinkedList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/CircleLinkedList.java @@ -1,8 +1,23 @@ package com.thealgorithms.datastructures.lists; +/** + * This class is a circular singly linked list implementation. In a circular linked list, + * the last node points back to the first node, creating a circular chain. + * + *

This implementation includes basic operations such as appending elements + * to the end, removing elements from a specified position, and converting + * the list to a string representation. + * + * @param the type of elements held in this list + */ public class CircleLinkedList { - private static final class Node { + /** + * A static nested class representing a node in the circular linked list. + * + * @param the type of element stored in the node + */ + static final class Node { Node next; E value; @@ -13,44 +28,56 @@ private Node(E value, Node next) { } } - // For better O.O design this should be private allows for better black box design private int size; - // this will point to dummy node; - private Node head = null; - private Node tail = null; // keeping a tail pointer to keep track of the end of list + Node head = null; + private Node tail; - // constructor for class.. here we will make a dummy node for circly linked list implementation - // with reduced error catching as our list will never be empty; + /** + * Initializes a new circular linked list. A dummy head node is used for simplicity, + * pointing initially to itself to ensure the list is never empty. + */ public CircleLinkedList() { - // creation of the dummy node - head = new Node(null, head); + head = new Node<>(null, head); tail = head; size = 0; } - // getter for the size... needed because size is private. + /** + * Returns the current size of the list. + * + * @return the number of elements in the list + */ public int getSize() { return size; } - // for the sake of simplistiy this class will only contain the append function or addLast other - // add functions can be implemented however this is the basses of them all really. + /** + * Appends a new element to the end of the list. Throws a NullPointerException if + * a null value is provided. + * + * @param value the value to append to the list + * @throws NullPointerException if the value is null + */ public void append(E value) { if (value == null) { - // we do not want to add null elements to the list. throw new NullPointerException("Cannot add null element to the list"); } - // head.next points to the last element; if (tail == null) { - tail = new Node(value, head); + tail = new Node<>(value, head); head.next = tail; } else { - tail.next = new Node(value, head); + tail.next = new Node<>(value, head); tail = tail.next; } size++; } + /** + * Returns a string representation of the list in the format "[ element1, element2, ... ]". + * An empty list is represented as "[]". + * + * @return the string representation of the list + */ public String toString() { if (size == 0) { return "[]"; @@ -68,23 +95,27 @@ public String toString() { return sb.toString(); } + /** + * Removes and returns the element at the specified position in the list. + * Throws an IndexOutOfBoundsException if the position is invalid. + * + * @param pos the position of the element to remove + * @return the value of the removed element + * @throws IndexOutOfBoundsException if the position is out of range + */ public E remove(int pos) { if (pos >= size || pos < 0) { - // catching errors - throw new IndexOutOfBoundsException("position cannot be greater than size or negative"); + throw new IndexOutOfBoundsException("Position out of bounds"); } - // we need to keep track of the element before the element we want to remove we can see why - // bellow. + Node before = head; for (int i = 1; i <= pos; i++) { before = before.next; } Node destroy = before.next; E saved = destroy.value; - // assigning the next reference to the element following the element we want to remove... - // the last element will be assigned to the head. - before.next = before.next.next; - // scrubbing + before.next = destroy.next; + if (destroy == tail) { tail = before; } diff --git a/src/test/java/com/thealgorithms/datastructures/lists/CircleLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/CircleLinkedListTest.java index b7a05ef29d6..883d2b02ba7 100644 --- a/src/test/java/com/thealgorithms/datastructures/lists/CircleLinkedListTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/CircleLinkedListTest.java @@ -1,78 +1,115 @@ package com.thealgorithms.datastructures.lists; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; public class CircleLinkedListTest { + private CircleLinkedList list; + + @BeforeEach + public void setUp() { + list = new CircleLinkedList<>(); + } + + @Test + public void testInitialSize() { + assertEquals(0, list.getSize(), "Initial size should be 0."); + } + @Test public void testAppendAndSize() { - CircleLinkedList list = new CircleLinkedList<>(); list.append(1); list.append(2); list.append(3); - assertEquals(3, list.getSize()); - assertEquals("[ 1, 2, 3 ]", list.toString()); + assertEquals(3, list.getSize(), "Size after three appends should be 3."); + assertEquals("[ 1, 2, 3 ]", list.toString(), "List content should match appended values."); } @Test public void testRemove() { - CircleLinkedList list = new CircleLinkedList<>(); list.append(1); list.append(2); list.append(3); list.append(4); - assertEquals(2, list.remove(1)); - assertEquals(3, list.remove(1)); - assertEquals("[ 1, 4 ]", list.toString()); - assertEquals(2, list.getSize()); + assertEquals(2, list.remove(1), "Removed element at index 1 should be 2."); + assertEquals(3, list.remove(1), "Removed element at index 1 after update should be 3."); + assertEquals("[ 1, 4 ]", list.toString(), "List content should reflect removals."); + assertEquals(2, list.getSize(), "Size after two removals should be 2."); } @Test public void testRemoveInvalidIndex() { - CircleLinkedList list = new CircleLinkedList<>(); list.append(1); list.append(2); - assertThrows(IndexOutOfBoundsException.class, () -> list.remove(2)); - assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1)); + assertThrows(IndexOutOfBoundsException.class, () -> list.remove(2), "Should throw on out-of-bounds index."); + assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1), "Should throw on negative index."); } @Test public void testToStringEmpty() { - CircleLinkedList list = new CircleLinkedList<>(); - assertEquals("[]", list.toString()); + assertEquals("[]", list.toString(), "Empty list should be represented by '[]'."); } @Test public void testToStringAfterRemoval() { - CircleLinkedList list = new CircleLinkedList<>(); list.append(1); list.append(2); list.append(3); list.remove(1); - assertEquals("[ 1, 3 ]", list.toString()); + assertEquals("[ 1, 3 ]", list.toString(), "List content should match remaining elements after removal."); } @Test public void testSingleElement() { - CircleLinkedList list = new CircleLinkedList<>(); list.append(1); - assertEquals(1, list.getSize()); - assertEquals("[ 1 ]", list.toString()); - assertEquals(1, list.remove(0)); - assertEquals("[]", list.toString()); + assertEquals(1, list.getSize(), "Size after single append should be 1."); + assertEquals("[ 1 ]", list.toString(), "Single element list should display properly."); + assertEquals(1, list.remove(0), "Single element removed should match appended value."); + assertEquals("[]", list.toString(), "List should be empty after removing the single element."); } @Test public void testNullElement() { - CircleLinkedList list = new CircleLinkedList<>(); - assertThrows(NullPointerException.class, () -> list.append(null)); + assertThrows(NullPointerException.class, () -> list.append(null), "Appending null should throw exception."); + } + + @Test + public void testCircularReference() { + list.append(1); + list.append(2); + list.append(3); + CircleLinkedList.Node current = list.head; + + // Traverse one full cycle and verify the circular reference + for (int i = 0; i <= list.getSize(); i++) { + current = current.next; + } + assertEquals(list.head, current, "End of list should point back to the head (circular structure)."); + } + + @Test + public void testClear() { + list.append(1); + list.append(2); + list.append(3); + + // Remove all elements to simulate clearing the list + for (int i = list.getSize() - 1; i >= 0; i--) { + list.remove(i); + } + + assertEquals(0, list.getSize(), "Size after clearing should be 0."); + assertEquals("[]", list.toString(), "Empty list should be represented by '[]' after clear."); + assertSame(list.head.next, list.head, "Head's next should point to itself after clearing."); } } From bc9645c0eadcba2283afbac8f8a769a0d5c05c78 Mon Sep 17 00:00:00 2001 From: PANKAJ PATWAL <120747214+Chiefpatwal@users.noreply.github.com> Date: Fri, 25 Oct 2024 23:08:26 +0530 Subject: [PATCH 69/75] Add Sliding Window algorithm and tests for maximum sum of subarray (#6001) --- .../slidingwindow/MaxSumKSizeSubarray.java | 50 ++++++++++++ .../MaxSumKSizeSubarrayTest.java | 79 +++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 src/main/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarray.java create mode 100644 src/test/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarrayTest.java diff --git a/src/main/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarray.java b/src/main/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarray.java new file mode 100644 index 00000000000..7e8095e08a0 --- /dev/null +++ b/src/main/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarray.java @@ -0,0 +1,50 @@ +package com.thealgorithms.slidingwindow; + +/** + * The Sliding Window algorithm is used to find the maximum sum of a subarray + * of a fixed size k within a given array. + * + *

+ * Worst-case performance O(n) + * Best-case performance O(n) + * Average performance O(n) + * Worst-case space complexity O(1) + * + * @author Your Name (https://github.com/Chiefpatwal) + */ +public final class MaxSumKSizeSubarray { + + // Prevent instantiation + private MaxSumKSizeSubarray() { + } + + /** + * This method finds the maximum sum of a subarray of a given size k. + * + * @param arr is the input array where the maximum sum needs to be found + * @param k is the size of the subarray + * @return the maximum sum of the subarray of size k + */ + public static int maxSumKSizeSubarray(int[] arr, int k) { + if (arr.length < k) { + return -1; // Edge case: not enough elements + } + + int maxSum; + int windowSum = 0; + + // Calculate the sum of the first window + for (int i = 0; i < k; i++) { + windowSum += arr[i]; + } + maxSum = windowSum; + + // Slide the window across the array + for (int i = k; i < arr.length; i++) { + windowSum += arr[i] - arr[i - k]; + maxSum = Math.max(maxSum, windowSum); + } + + return maxSum; + } +} diff --git a/src/test/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarrayTest.java b/src/test/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarrayTest.java new file mode 100644 index 00000000000..aa3c2eae329 --- /dev/null +++ b/src/test/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarrayTest.java @@ -0,0 +1,79 @@ +package com.thealgorithms.slidingwindow; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the MaxSumKSizeSubarray class. + * + * @author Your Name (https://github.com/Chiefpatwal) + */ +class MaxSumKSizeSubarrayTest { + + /** + * Test for the basic case of finding the maximum sum. + */ + @Test + void testMaxSumKSizeSubarray() { + int[] arr = {1, 2, 3, 4, 5}; + int k = 2; + int expectedMaxSum = 9; // 4 + 5 + assertEquals(expectedMaxSum, MaxSumKSizeSubarray.maxSumKSizeSubarray(arr, k)); + } + + /** + * Test for a different array and subarray size. + */ + @Test + void testMaxSumKSizeSubarrayWithDifferentValues() { + int[] arr = {2, 1, 5, 1, 3, 2}; + int k = 3; + int expectedMaxSum = 9; // 5 + 1 + 3 + assertEquals(expectedMaxSum, MaxSumKSizeSubarray.maxSumKSizeSubarray(arr, k)); + } + + /** + * Test for edge case with insufficient elements. + */ + @Test + void testMaxSumKSizeSubarrayWithInsufficientElements() { + int[] arr = {1, 2}; + int k = 3; // Not enough elements + int expectedMaxSum = -1; // Edge case + assertEquals(expectedMaxSum, MaxSumKSizeSubarray.maxSumKSizeSubarray(arr, k)); + } + + /** + * Test for large array. + */ + @Test + void testMaxSumKSizeSubarrayWithLargeArray() { + int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + int k = 5; + int expectedMaxSum = 40; // 6 + 7 + 8 + 9 + 10 + assertEquals(expectedMaxSum, MaxSumKSizeSubarray.maxSumKSizeSubarray(arr, k)); + } + + /** + * Test for array with negative numbers. + */ + @Test + void testMaxSumKSizeSubarrayWithNegativeNumbers() { + int[] arr = {-1, -2, -3, -4, -5}; + int k = 2; + int expectedMaxSum = -3; // -1 + -2 + assertEquals(expectedMaxSum, MaxSumKSizeSubarray.maxSumKSizeSubarray(arr, k)); + } + + /** + * Test for the case where k equals the array length. + */ + @Test + void testMaxSumKSizeSubarrayWithKEqualToArrayLength() { + int[] arr = {1, 2, 3, 4, 5}; + int k = 5; + int expectedMaxSum = 15; // 1 + 2 + 3 + 4 + 5 + assertEquals(expectedMaxSum, MaxSumKSizeSubarray.maxSumKSizeSubarray(arr, k)); + } +} From e154a501057f8bab3697531d0246f6eb2629d6e6 Mon Sep 17 00:00:00 2001 From: KhalidDev <148249284+devAlq@users.noreply.github.com> Date: Fri, 25 Oct 2024 20:48:00 +0300 Subject: [PATCH 70/75] Fix typo in README.md (#5895) --- src/main/java/com/thealgorithms/datastructures/lists/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/datastructures/lists/README.md b/src/main/java/com/thealgorithms/datastructures/lists/README.md index ea389c0422c..6aefa4c98e6 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/README.md +++ b/src/main/java/com/thealgorithms/datastructures/lists/README.md @@ -1,7 +1,7 @@ ## Linked List ### Description -LinkedList is a data structure in which data is stored in a linear manner. It usually contains a data field and a link to the memory location of the next mode. +LinkedList is a data structure in which data is stored in a linear manner. It usually contains a data field and a link to the memory location of the next node. ### Structure From 3b2ba488bbcd8c2ed5d8e6c4d4596b67d44c4593 Mon Sep 17 00:00:00 2001 From: Ayush Kumar <119495476+SuprHUlk@users.noreply.github.com> Date: Fri, 25 Oct 2024 23:21:31 +0530 Subject: [PATCH 71/75] Add chinese remainder theorem (#5873) --- .../maths/ChineseRemainderTheorem.java | 84 +++++++++++++++++++ .../maths/ChineseRemainderTheoremTest.java | 54 ++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 src/main/java/com/thealgorithms/maths/ChineseRemainderTheorem.java create mode 100644 src/test/java/com/thealgorithms/maths/ChineseRemainderTheoremTest.java diff --git a/src/main/java/com/thealgorithms/maths/ChineseRemainderTheorem.java b/src/main/java/com/thealgorithms/maths/ChineseRemainderTheorem.java new file mode 100644 index 00000000000..c26e67cffb5 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/ChineseRemainderTheorem.java @@ -0,0 +1,84 @@ +package com.thealgorithms.maths; + +import java.util.List; + +/** + * @brief Implementation of the Chinese Remainder Theorem (CRT) algorithm + * @details + * The Chinese Remainder Theorem (CRT) is used to solve systems of + * simultaneous congruences. Given several pairwise coprime moduli + * and corresponding remainders, the algorithm finds the smallest + * positive solution. + */ +public final class ChineseRemainderTheorem { + private ChineseRemainderTheorem() { + } + + /** + * @brief Solves the Chinese Remainder Theorem problem. + * @param remainders The list of remainders. + * @param moduli The list of pairwise coprime moduli. + * @return The smallest positive solution that satisfies all the given congruences. + */ + public static int solveCRT(List remainders, List moduli) { + int product = 1; + int result = 0; + + // Calculate the product of all moduli + for (int mod : moduli) { + product *= mod; + } + + // Apply the formula for each congruence + for (int i = 0; i < moduli.size(); i++) { + int partialProduct = product / moduli.get(i); + int inverse = modInverse(partialProduct, moduli.get(i)); + result += remainders.get(i) * partialProduct * inverse; + } + + // Adjust result to be the smallest positive solution + result = result % product; + if (result < 0) { + result += product; + } + + return result; + } + + /** + * @brief Computes the modular inverse of a number with respect to a modulus using + * the Extended Euclidean Algorithm. + * @param a The number for which to find the inverse. + * @param m The modulus. + * @return The modular inverse of a modulo m. + */ + private static int modInverse(int a, int m) { + int m0 = m; + int x0 = 0; + int x1 = 1; + + if (m == 1) { + return 0; + } + + while (a > 1) { + int q = a / m; + int t = m; + + // m is remainder now, process same as Euclid's algorithm + m = a % m; + a = t; + t = x0; + + x0 = x1 - q * x0; + x1 = t; + } + + // Make x1 positive + if (x1 < 0) { + x1 += m0; + } + + return x1; + } +} diff --git a/src/test/java/com/thealgorithms/maths/ChineseRemainderTheoremTest.java b/src/test/java/com/thealgorithms/maths/ChineseRemainderTheoremTest.java new file mode 100644 index 00000000000..31c676d6e7b --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/ChineseRemainderTheoremTest.java @@ -0,0 +1,54 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class ChineseRemainderTheoremTest { + @Test + public void testCRTSimpleCase() { + List remainders = Arrays.asList(2, 3, 2); + List moduli = Arrays.asList(3, 5, 7); + int expected = 23; + int result = ChineseRemainderTheorem.solveCRT(remainders, moduli); + assertEquals(expected, result); + } + + @Test + public void testCRTLargeModuli() { + List remainders = Arrays.asList(1, 2, 3); + List moduli = Arrays.asList(5, 7, 9); + int expected = 156; + int result = ChineseRemainderTheorem.solveCRT(remainders, moduli); + assertEquals(expected, result); + } + + @Test + public void testCRTWithSingleCongruence() { + List remainders = Arrays.asList(4); + List moduli = Arrays.asList(7); + int expected = 4; + int result = ChineseRemainderTheorem.solveCRT(remainders, moduli); + assertEquals(expected, result); + } + + @Test + public void testCRTWithMultipleSolutions() { + List remainders = Arrays.asList(0, 3); + List moduli = Arrays.asList(4, 5); + int expected = 8; + int result = ChineseRemainderTheorem.solveCRT(remainders, moduli); + assertEquals(expected, result); + } + + @Test + public void testCRTLargeNumbers() { + List remainders = Arrays.asList(0, 4, 6); + List moduli = Arrays.asList(11, 13, 17); + int expected = 550; + int result = ChineseRemainderTheorem.solveCRT(remainders, moduli); + assertEquals(expected, result); + } +} From 131e5381be23718cda8ce1a56adaeb0f972afdc8 Mon Sep 17 00:00:00 2001 From: Giulio Tantaro Date: Fri, 25 Oct 2024 19:56:54 +0200 Subject: [PATCH 72/75] Add tests Generic Heap and add check null item (#5801) --- .../datastructures/heaps/GenericHeap.java | 4 + .../datastructures/heaps/GenericHeapTest.java | 126 ++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 src/test/java/com/thealgorithms/datastructures/heaps/GenericHeapTest.java diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/GenericHeap.java b/src/main/java/com/thealgorithms/datastructures/heaps/GenericHeap.java index d546b7cc88d..f1772b5b311 100644 --- a/src/main/java/com/thealgorithms/datastructures/heaps/GenericHeap.java +++ b/src/main/java/com/thealgorithms/datastructures/heaps/GenericHeap.java @@ -9,6 +9,10 @@ public class GenericHeap> { HashMap map = new HashMap<>(); public void add(T item) { + if (item == null) { + throw new IllegalArgumentException("Cannot insert null into the heap."); + } + this.data.add(item); map.put(item, this.data.size() - 1); // upHeapify(this.data.size() - 1); diff --git a/src/test/java/com/thealgorithms/datastructures/heaps/GenericHeapTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/GenericHeapTest.java new file mode 100644 index 00000000000..8915a6d8aef --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/heaps/GenericHeapTest.java @@ -0,0 +1,126 @@ +package com.thealgorithms.datastructures.heaps; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class GenericHeapTest { + + private GenericHeap heap; + + @BeforeEach + public void setUp() { + heap = new GenericHeap<>(); + } + + @Test + public void testGenericHeapAddAndGet() { + heap.add(19); + heap.add(36); + heap.add(100); + heap.add(-17); + heap.add(3); + + // Check that the largest element (100) is at the top of the heap + assertEquals(100, heap.get()); + } + + @Test + public void testGenericHeapRemove() { + heap.add(19); + heap.add(36); + heap.add(100); + heap.add(-17); + heap.add(3); + + // Verify that the largest element is removed correctly + assertEquals(100, heap.remove()); + + // The new element at the top should be 36 + assertEquals(36, heap.get()); + + // Check that the size is correct after removal + assertEquals(4, heap.size()); + } + + @Test + public void testGenericHeapSize() { + assertTrue(heap.isEmpty()); + + heap.add(10); + heap.add(20); + + // Check that the size is correct + assertEquals(2, heap.size()); + + heap.remove(); + + // After removal, the size should be 1 + assertEquals(1, heap.size()); + } + + @Test + public void testGenericHeapIsEmpty() { + // Verify that the heap is initially empty + assertTrue(heap.isEmpty()); + + heap.add(15); + + // Now the heap should not be empty + assertFalse(heap.isEmpty()); + + heap.remove(); + + // After removing the one element, it should be empty again + assertTrue(heap.isEmpty()); + } + + @Test + public void testGenericHeapUpdatePriority() { + heap.add(19); + heap.add(36); + heap.add(100); + heap.add(-17); + heap.add(3); + + // Verify that the largest element initially is 100 + assertEquals(100, heap.get()); + + heap.remove(); + + // Simulates a change in priority by increasing the value of 100 to 44 + heap.add(44); + + // Now, the new high should be 25 + assertEquals(44, heap.get()); + } + + @Test + public void testGenericHeapRemoveUntilEmpty() { + heap.add(5); + heap.add(3); + heap.add(4); + heap.add(1); + heap.add(2); + + // Remove all items and check that they are removed in descending order + assertEquals(5, heap.remove()); + assertEquals(4, heap.remove()); + assertEquals(3, heap.remove()); + assertEquals(2, heap.remove()); + assertEquals(1, heap.remove()); + + // Empty heap + assertTrue(heap.isEmpty()); + } + + @Test + public void testGenericHeapAddNullItem() { + // Check null item + assertThrows(IllegalArgumentException.class, () -> { heap.add(null); }); + } +} From cebefbc8b60cb6825960cecc24d90b0f3cb6f793 Mon Sep 17 00:00:00 2001 From: Giulio Tantaro Date: Fri, 25 Oct 2024 21:18:32 +0200 Subject: [PATCH 73/75] Add tests Tower of Hanoi (#5736) From 135fb08224046de3d607391c89e37287b6f9e41a Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Sat, 26 Oct 2024 00:53:20 +0530 Subject: [PATCH 74/75] Add `EgyptianFraction` algorithm (#5804) --- DIRECTORY.md | 9 +++++ .../greedyalgorithms/EgyptianFraction.java | 35 +++++++++++++++++++ .../EgyptianFractionTest.java | 22 ++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 src/main/java/com/thealgorithms/greedyalgorithms/EgyptianFraction.java create mode 100644 src/test/java/com/thealgorithms/greedyalgorithms/EgyptianFractionTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 744e16db8d6..98fbec625f5 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -321,6 +321,7 @@ * [BinaryAddition](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/BinaryAddition.java) * [CoinChange](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/CoinChange.java) * [DigitSeparation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/DigitSeparation.java) + * [EgyptianFraction](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/EgyptianFraction.java) * [FractionalKnapsack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/FractionalKnapsack.java) * [GaleShapley](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/GaleShapley.java) * [JobSequencing](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/JobSequencing.java) @@ -351,6 +352,7 @@ * [BinaryPow](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/BinaryPow.java) * [BinomialCoefficient](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/BinomialCoefficient.java) * [Ceil](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Ceil.java) + * [ChineseRemainderTheorem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/ChineseRemainderTheorem.java) * [CircularConvolutionFFT](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/CircularConvolutionFFT.java) * [CollatzConjecture](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/CollatzConjecture.java) * [Combinations](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Combinations.java) @@ -568,6 +570,8 @@ * [TernarySearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/TernarySearch.java) * [UnionFind](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/UnionFind.java) * [UpperBound](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/UpperBound.java) + * slidingwindow + * [MaxSumKSizeSubarray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarray.java) * sorts * [AdaptiveMergeSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/AdaptiveMergeSort.java) * [BeadSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/BeadSort.java) @@ -826,6 +830,7 @@ * [HashMapCuckooHashingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/HashMapCuckooHashingTest.java) * heaps * [FibonacciHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/FibonacciHeapTest.java) + * [GenericHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/GenericHeapTest.java) * [LeftistHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/LeftistHeapTest.java) * lists * [CircleLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CircleLinkedListTest.java) @@ -942,6 +947,7 @@ * [BinaryAdditionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/BinaryAdditionTest.java) * [CoinChangeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/CoinChangeTest.java) * [DigitSeparationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/DigitSeparationTest.java) + * [EgyptianFractionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/EgyptianFractionTest.java) * [FractionalKnapsackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/FractionalKnapsackTest.java) * [GaleShapleyTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/GaleShapleyTest.java) * [JobSequencingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/JobSequencingTest.java) @@ -969,6 +975,7 @@ * [BinaryPowTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/BinaryPowTest.java) * [BinomialCoefficientTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/BinomialCoefficientTest.java) * [CeilTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/CeilTest.java) + * [ChineseRemainderTheoremTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/ChineseRemainderTheoremTest.java) * [CollatzConjectureTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/CollatzConjectureTest.java) * [CombinationsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/CombinationsTest.java) * [ConvolutionFFTTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/ConvolutionFFTTest.java) @@ -1155,6 +1162,8 @@ * [TestSearchInARowAndColWiseSortedMatrix](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/TestSearchInARowAndColWiseSortedMatrix.java) * [UnionFindTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/UnionFindTest.java) * [UpperBoundTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/UpperBoundTest.java) + * slidingwindow + * [MaxSumKSizeSubarrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarrayTest.java) * sorts * [AdaptiveMergeSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/AdaptiveMergeSortTest.java) * [BeadSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/BeadSortTest.java) diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/EgyptianFraction.java b/src/main/java/com/thealgorithms/greedyalgorithms/EgyptianFraction.java new file mode 100644 index 00000000000..35cbfe876b0 --- /dev/null +++ b/src/main/java/com/thealgorithms/greedyalgorithms/EgyptianFraction.java @@ -0,0 +1,35 @@ +package com.thealgorithms.greedyalgorithms; + +import java.util.ArrayList; +import java.util.List; + +/** + * Class to represent a fraction as a sum of unique unit fractions. + * Example: + * 2/3 = 1/2 + 1/6 + * 3/10 = 1/4 + 1/20 + * + * @author Hardvan + */ +public final class EgyptianFraction { + private EgyptianFraction() { + } + + /** + * Calculates the Egyptian Fraction representation of a given fraction. + * + * @param numerator the numerator of the fraction + * @param denominator the denominator of the fraction + * @return List of unit fractions represented as strings "1/x" + */ + public static List getEgyptianFraction(int numerator, int denominator) { + List result = new ArrayList<>(); + while (numerator != 0) { + int x = (int) Math.ceil((double) denominator / numerator); + result.add("1/" + x); + numerator = numerator * x - denominator; + denominator = denominator * x; + } + return result; + } +} diff --git a/src/test/java/com/thealgorithms/greedyalgorithms/EgyptianFractionTest.java b/src/test/java/com/thealgorithms/greedyalgorithms/EgyptianFractionTest.java new file mode 100644 index 00000000000..1d34876de86 --- /dev/null +++ b/src/test/java/com/thealgorithms/greedyalgorithms/EgyptianFractionTest.java @@ -0,0 +1,22 @@ +package com.thealgorithms.greedyalgorithms; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class EgyptianFractionTest { + + @ParameterizedTest + @MethodSource("fractionProvider") + public void testGetEgyptianFraction(int numerator, int denominator, List expected) { + assertEquals(expected, EgyptianFraction.getEgyptianFraction(numerator, denominator)); + } + + private static Stream fractionProvider() { + return Stream.of(Arguments.of(2, 3, List.of("1/2", "1/6")), Arguments.of(3, 10, List.of("1/4", "1/20")), Arguments.of(1, 3, List.of("1/3")), Arguments.of(1, 2, List.of("1/2")), Arguments.of(4, 13, List.of("1/4", "1/18", "1/468"))); + } +} From 0f1dcbe47930d9a30f1beaee359c183669b4d1fb Mon Sep 17 00:00:00 2001 From: PANKAJ PATWAL <120747214+Chiefpatwal@users.noreply.github.com> Date: Sat, 26 Oct 2024 01:48:40 +0530 Subject: [PATCH 75/75] Add longest substring (#6007) --- ...stSubstringWithoutRepeatingCharacters.java | 46 +++++++++++++++++++ ...bstringWithoutRepeatingCharactersTest.java | 23 ++++++++++ 2 files changed, 69 insertions(+) create mode 100644 src/main/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharacters.java create mode 100644 src/test/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharactersTest.java diff --git a/src/main/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharacters.java b/src/main/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharacters.java new file mode 100644 index 00000000000..0641730d8b0 --- /dev/null +++ b/src/main/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharacters.java @@ -0,0 +1,46 @@ +package com.thealgorithms.slidingwindow; +import java.util.HashSet; + +/** + * The Longest Substring Without Repeating Characters algorithm finds the length of + * the longest substring without repeating characters in a given string. + * + *

+ * Worst-case performance O(n) + * Best-case performance O(n) + * Average performance O(n) + * Worst-case space complexity O(min(n, m)), where n is the length of the string + * and m is the size of the character set. + * + * @author (https://github.com/Chiefpatwal) + */ +public final class LongestSubstringWithoutRepeatingCharacters { + + // Prevent instantiation + private LongestSubstringWithoutRepeatingCharacters() { + } + + /** + * This method finds the length of the longest substring without repeating characters. + * + * @param s is the input string + * @return the length of the longest substring without repeating characters + */ + public static int lengthOfLongestSubstring(String s) { + int maxLength = 0; + int left = 0; + HashSet charSet = new HashSet<>(); + + for (int right = 0; right < s.length(); right++) { + // If the character is already in the set, remove characters from the left + while (charSet.contains(s.charAt(right))) { + charSet.remove(s.charAt(left)); + left++; + } + charSet.add(s.charAt(right)); + maxLength = Math.max(maxLength, right - left + 1); + } + + return maxLength; + } +} diff --git a/src/test/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharactersTest.java b/src/test/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharactersTest.java new file mode 100644 index 00000000000..8638a707a3e --- /dev/null +++ b/src/test/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharactersTest.java @@ -0,0 +1,23 @@ +package com.thealgorithms.slidingwindow; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the LongestSubstringWithoutRepeatingCharacters class. + * + * @author (https://github.com/Chiefpatwal) + */ +public class LongestSubstringWithoutRepeatingCharactersTest { + + @Test + public void testLengthOfLongestSubstring() { + // Test cases for the lengthOfLongestSubstring method + assertEquals(3, LongestSubstringWithoutRepeatingCharacters.lengthOfLongestSubstring("abcabcbb")); + assertEquals(1, LongestSubstringWithoutRepeatingCharacters.lengthOfLongestSubstring("bbbbb")); + assertEquals(3, LongestSubstringWithoutRepeatingCharacters.lengthOfLongestSubstring("pwwkew")); + assertEquals(0, LongestSubstringWithoutRepeatingCharacters.lengthOfLongestSubstring("")); + assertEquals(5, LongestSubstringWithoutRepeatingCharacters.lengthOfLongestSubstring("abcde")); + } +}