From 6faef1ad7d247a187c791a3688e7498047db12d6 Mon Sep 17 00:00:00 2001 From: Divyansh Saxena Date: Fri, 30 Jan 2026 12:41:57 +0530 Subject: [PATCH 1/7] feat: add graph cycle detection algorithm and its unit tests. --- pom.xml | 1 + .../datastructures/graphs/Cycles.java | 73 +++++++++--------- .../datastructures/graphs/CyclesTest.java | 76 +++++++++++++++++++ 3 files changed, 112 insertions(+), 38 deletions(-) create mode 100644 src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java diff --git a/pom.xml b/pom.xml index 170e3900b77f..978c774d6caa 100644 --- a/pom.xml +++ b/pom.xml @@ -76,6 +76,7 @@ -Xlint:all -Xlint:-auxiliaryclass -Werror + diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java b/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java index aea2b74bd13b..9278a64f08bf 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java @@ -1,59 +1,42 @@ package com.thealgorithms.datastructures.graphs; import java.util.ArrayList; -import java.util.Scanner; +import java.util.List; class Cycle { private final int nodes; - private int[][] adjacencyMatrix; - private boolean[] visited; - ArrayList> cycles = new ArrayList>(); - - Cycle() { - Scanner in = new Scanner(System.in); - System.out.print("Enter the no. of nodes: "); - nodes = in.nextInt(); - System.out.print("Enter the no. of Edges: "); - final int edges = in.nextInt(); - - adjacencyMatrix = new int[nodes][nodes]; - visited = new boolean[nodes]; + private final int[][] adjacencyMatrix; + private final boolean[] visited; + private final List> cycles = new ArrayList<>(); + Cycle(int nodes, int[][] adjacencyMatrix) { + this.nodes = nodes; + this.adjacencyMatrix = new int[nodes][nodes]; + // Deep copy matrix to avoid side-effects for (int i = 0; i < nodes; i++) { - visited[i] = false; - } - - System.out.println("Enter the details of each edges "); - - for (int i = 0; i < edges; i++) { - int start; - int end; - start = in.nextInt(); - end = in.nextInt(); - adjacencyMatrix[start][end] = 1; + System.arraycopy(adjacencyMatrix[i], 0, this.adjacencyMatrix[i], 0, nodes); } - in.close(); + this.visited = new boolean[nodes]; } public void start() { for (int i = 0; i < nodes; i++) { - ArrayList temp = new ArrayList<>(); - dfs(i, i, temp); + dfs(i, i, new ArrayList<>()); for (int j = 0; j < nodes; j++) { - adjacencyMatrix[i][j] = 0; - adjacencyMatrix[j][i] = 0; + this.adjacencyMatrix[i][j] = 0; + this.adjacencyMatrix[j][i] = 0; } } } - private void dfs(Integer start, Integer curr, ArrayList temp) { + private void dfs(int start, int curr, List temp) { temp.add(curr); visited[curr] = true; for (int i = 0; i < nodes; i++) { if (adjacencyMatrix[curr][i] == 1) { if (i == start) { - cycles.add(new ArrayList(temp)); + cycles.add(new ArrayList<>(temp)); } else { if (!visited[i]) { dfs(start, i, temp); @@ -62,18 +45,24 @@ private void dfs(Integer start, Integer curr, ArrayList temp) { } } - if (temp.size() > 0) { + if (!temp.isEmpty()) { temp.remove(temp.size() - 1); } visited[curr] = false; } + public List> getCycles() { + return cycles; + } + public void printAll() { - for (int i = 0; i < cycles.size(); i++) { - for (int j = 0; j < cycles.get(i).size(); j++) { - System.out.print(cycles.get(i).get(j) + " -> "); + for (List cycle : cycles) { + for (Integer node : cycle) { + System.out.print(node + " -> "); + } + if (!cycle.isEmpty()) { + System.out.println(cycle.get(0)); } - System.out.println(cycles.get(i).get(0)); System.out.println(); } } @@ -84,7 +73,15 @@ private Cycles() { } public static void main(String[] args) { - Cycle c = new Cycle(); + // Example usage with a triangle graph: 0 -> 1 -> 2 -> 0 + int nodes = 3; + int[][] matrix = { + { 0, 1, 1 }, + { 1, 0, 1 }, + { 1, 1, 0 } + }; + + Cycle c = new Cycle(nodes, matrix); c.start(); c.printAll(); } diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java new file mode 100644 index 000000000000..96ae89e54e08 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java @@ -0,0 +1,76 @@ +package com.thealgorithms.datastructures.graphs; + +import org.junit.jupiter.api.Test; +import java.util.List; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CyclesTest { + + @Test + void testTriangleCycle() { + // Triangle graph: 0-1, 1-2, 2-0 + int nodes = 3; + int[][] matrix = { + { 0, 1, 1 }, + { 1, 0, 1 }, + { 1, 1, 0 } + }; + + Cycle c = new Cycle(nodes, matrix); + c.start(); + List> cycles = c.getCycles(); + + // Should find at least one cycle: [0, 1, 2] + // Note: The algorithm modifies the matrix as it goes, so it finds elementary + // cycles. + // For a triangle, it finds 0-1-2. + + assertFalse(cycles.isEmpty(), "Should detect at least one cycle"); + // Verify the cycle content + boolean foundTriangle = false; + for (List cycle : cycles) { + if (cycle.size() == 3 && cycle.contains(0) && cycle.contains(1) && cycle.contains(2)) { + foundTriangle = true; + break; + } + } + assertTrue(foundTriangle, "Should detect the 0-1-2 triangle"); + } + + @Test + void testNoCycle() { + // Line graph: 0 -> 1 -> 2 + int nodes = 3; + int[][] matrix = { + { 0, 1, 0 }, + { 0, 0, 1 }, + { 0, 0, 0 } + }; + + Cycle c = new Cycle(nodes, matrix); + c.start(); + List> cycles = c.getCycles(); + + assertTrue(cycles.isEmpty(), "Should not detect any cycles in a line graph"); + } + + @Test + void testSelfLoop() { + // Node 0 has self loop + int nodes = 1; + int[][] matrix = { + { 1 } + }; + + Cycle c = new Cycle(nodes, matrix); + c.start(); + List> cycles = c.getCycles(); + + assertFalse(cycles.isEmpty(), "Should detect self loop"); + assertEquals(1, cycles.size()); + assertEquals(1, cycles.get(0).size()); + assertEquals(0, cycles.get(0).get(0)); + } +} From 48bedf814160bde96e198bb2281eb8cbdd940b3e Mon Sep 17 00:00:00 2001 From: Divyansh Saxena Date: Fri, 30 Jan 2026 12:53:46 +0530 Subject: [PATCH 2/7] Refactor Cycles.java and add tests --- .../datastructures/graphs/CyclesTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java index 96ae89e54e08..f129064fd1c4 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java @@ -73,4 +73,22 @@ void testSelfLoop() { assertEquals(1, cycles.get(0).size()); assertEquals(0, cycles.get(0).get(0)); } + + @Test + void testPrintAll() { + int nodes = 3; + int[][] matrix = { + { 0, 1, 1 }, + { 1, 0, 1 }, + { 1, 1, 0 } + }; + Cycle c = new Cycle(nodes, matrix); + c.start(); + c.printAll(); // Ensure no exception + } + + @Test + void testMain() { + Cycles.main(new String[] {}); + } } From 792c7d666e25e774d20d924c36ca94303e35727c Mon Sep 17 00:00:00 2001 From: Divyansh Saxena Date: Fri, 30 Jan 2026 13:50:28 +0530 Subject: [PATCH 3/7] Fix import order for formatting --- .../com/thealgorithms/datastructures/graphs/CyclesTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java index f129064fd1c4..9d5ff3cb8466 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java @@ -1,11 +1,12 @@ package com.thealgorithms.datastructures.graphs; -import org.junit.jupiter.api.Test; -import java.util.List; 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; + class CyclesTest { @Test From 97396c2c3fea62739bc0c442a1007ff3ab8995df Mon Sep 17 00:00:00 2001 From: Divyansh Saxena Date: Fri, 30 Jan 2026 13:56:22 +0530 Subject: [PATCH 4/7] Optimize imports spacing --- .../java/com/thealgorithms/datastructures/graphs/CyclesTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java index 9d5ff3cb8466..25dcace85962 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; + import org.junit.jupiter.api.Test; class CyclesTest { From 0ed5552288035cf1eb68f30e750137238e087a68 Mon Sep 17 00:00:00 2001 From: Divyansh Saxena Date: Fri, 30 Jan 2026 14:01:04 +0530 Subject: [PATCH 5/7] Format array initializers for Clang Format --- .../datastructures/graphs/Cycles.java | 6 +---- .../datastructures/graphs/CyclesTest.java | 22 ++++--------------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java b/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java index 9278a64f08bf..a43940056d95 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java @@ -75,11 +75,7 @@ private Cycles() { public static void main(String[] args) { // Example usage with a triangle graph: 0 -> 1 -> 2 -> 0 int nodes = 3; - int[][] matrix = { - { 0, 1, 1 }, - { 1, 0, 1 }, - { 1, 1, 0 } - }; + int[][] matrix = { { 0, 1, 1 }, { 1, 0, 1 }, { 1, 1, 0 } }; Cycle c = new Cycle(nodes, matrix); c.start(); diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java index 25dcace85962..f8ec19b184b8 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java @@ -14,11 +14,7 @@ class CyclesTest { void testTriangleCycle() { // Triangle graph: 0-1, 1-2, 2-0 int nodes = 3; - int[][] matrix = { - { 0, 1, 1 }, - { 1, 0, 1 }, - { 1, 1, 0 } - }; + int[][] matrix = { { 0, 1, 1 }, { 1, 0, 1 }, { 1, 1, 0 } }; Cycle c = new Cycle(nodes, matrix); c.start(); @@ -45,11 +41,7 @@ void testTriangleCycle() { void testNoCycle() { // Line graph: 0 -> 1 -> 2 int nodes = 3; - int[][] matrix = { - { 0, 1, 0 }, - { 0, 0, 1 }, - { 0, 0, 0 } - }; + int[][] matrix = { { 0, 1, 0 }, { 0, 0, 1 }, { 0, 0, 0 } }; Cycle c = new Cycle(nodes, matrix); c.start(); @@ -62,9 +54,7 @@ void testNoCycle() { void testSelfLoop() { // Node 0 has self loop int nodes = 1; - int[][] matrix = { - { 1 } - }; + int[][] matrix = { { 1 } }; Cycle c = new Cycle(nodes, matrix); c.start(); @@ -79,11 +69,7 @@ void testSelfLoop() { @Test void testPrintAll() { int nodes = 3; - int[][] matrix = { - { 0, 1, 1 }, - { 1, 0, 1 }, - { 1, 1, 0 } - }; + int[][] matrix = { { 0, 1, 1 }, { 1, 0, 1 }, { 1, 1, 0 } }; Cycle c = new Cycle(nodes, matrix); c.start(); c.printAll(); // Ensure no exception From 801460e50bfaddcfaa04c3733f507d60aa644dc2 Mon Sep 17 00:00:00 2001 From: Divyansh Saxena Date: Fri, 30 Jan 2026 14:19:54 +0530 Subject: [PATCH 6/7] Apply clang-format using .clang-format config --- .../com/thealgorithms/datastructures/graphs/Cycles.java | 2 +- .../thealgorithms/datastructures/graphs/CyclesTest.java | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java b/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java index a43940056d95..a9b2390dfbbc 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java @@ -75,7 +75,7 @@ private Cycles() { public static void main(String[] args) { // Example usage with a triangle graph: 0 -> 1 -> 2 -> 0 int nodes = 3; - int[][] matrix = { { 0, 1, 1 }, { 1, 0, 1 }, { 1, 1, 0 } }; + int[][] matrix = {{0, 1, 1}, {1, 0, 1}, {1, 1, 0}}; Cycle c = new Cycle(nodes, matrix); c.start(); diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java index f8ec19b184b8..dfe81c9a85d2 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/CyclesTest.java @@ -5,7 +5,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; - import org.junit.jupiter.api.Test; class CyclesTest { @@ -14,7 +13,7 @@ class CyclesTest { void testTriangleCycle() { // Triangle graph: 0-1, 1-2, 2-0 int nodes = 3; - int[][] matrix = { { 0, 1, 1 }, { 1, 0, 1 }, { 1, 1, 0 } }; + int[][] matrix = {{0, 1, 1}, {1, 0, 1}, {1, 1, 0}}; Cycle c = new Cycle(nodes, matrix); c.start(); @@ -41,7 +40,7 @@ void testTriangleCycle() { void testNoCycle() { // Line graph: 0 -> 1 -> 2 int nodes = 3; - int[][] matrix = { { 0, 1, 0 }, { 0, 0, 1 }, { 0, 0, 0 } }; + int[][] matrix = {{0, 1, 0}, {0, 0, 1}, {0, 0, 0}}; Cycle c = new Cycle(nodes, matrix); c.start(); @@ -54,7 +53,7 @@ void testNoCycle() { void testSelfLoop() { // Node 0 has self loop int nodes = 1; - int[][] matrix = { { 1 } }; + int[][] matrix = {{1}}; Cycle c = new Cycle(nodes, matrix); c.start(); @@ -69,7 +68,7 @@ void testSelfLoop() { @Test void testPrintAll() { int nodes = 3; - int[][] matrix = { { 0, 1, 1 }, { 1, 0, 1 }, { 1, 1, 0 } }; + int[][] matrix = {{0, 1, 1}, {1, 0, 1}, {1, 1, 0}}; Cycle c = new Cycle(nodes, matrix); c.start(); c.printAll(); // Ensure no exception From f998f62353b283e3878ad580b58f0ca1e012cedc Mon Sep 17 00:00:00 2001 From: Divyansh Saxena Date: Fri, 30 Jan 2026 14:35:53 +0530 Subject: [PATCH 7/7] Normalize line endings to LF --- .gitattributes | 1 + .../bitmanipulation/SingleBitOperations.java | 136 ++-- .../thealgorithms/ciphers/AtbashCipher.java | 202 +++--- .../ciphers/PermutationCipher.java | 388 +++++------ .../ciphers/RailFenceCipher.java | 294 ++++---- .../DisjointSetUnionBySize.java | 166 ++--- .../UniqueSubsequencesCount.java | 196 +++--- .../others/IterativeFloodFill.java | 204 +++--- .../SearchInARowAndColWiseSortedMatrix.java | 64 +- .../thealgorithms/strings/AhoCorasick.java | 492 ++++++------- .../com/thealgorithms/ciphers/AtbashTest.java | 86 +-- .../ciphers/PermutationCipherTest.java | 648 +++++++++--------- .../thealgorithms/ciphers/RailFenceTest.java | 124 ++-- .../DisjointSetUnionBySizeTest.java | 292 ++++---- .../lists/CircularDoublyLinkedListTest.java | 240 +++---- .../UniqueSubsequencesCountTest.java | 30 +- .../maths/VampireNumberTest.java | 64 +- .../others/IterativeFloodFillTest.java | 326 ++++----- ...estSearchInARowAndColWiseSortedMatrix.java | 50 +- 19 files changed, 2002 insertions(+), 2001 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000000..6313b56c5784 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/src/main/java/com/thealgorithms/bitmanipulation/SingleBitOperations.java b/src/main/java/com/thealgorithms/bitmanipulation/SingleBitOperations.java index 624a4e2b858a..7cf1e43dabc1 100644 --- a/src/main/java/com/thealgorithms/bitmanipulation/SingleBitOperations.java +++ b/src/main/java/com/thealgorithms/bitmanipulation/SingleBitOperations.java @@ -1,68 +1,68 @@ -package com.thealgorithms.bitmanipulation; - -/** - * A utility class for performing single-bit operations on integers. - * These operations include flipping, setting, clearing, and getting - * individual bits at specified positions. - * - * Bit positions are zero-indexed (i.e., the least significant bit is at position 0). - * These methods leverage bitwise operations for optimal performance. - * - * Examples: - * - `flipBit(3, 1)` flips the bit at index 1 in binary `11` (result: `1`). - * - `setBit(4, 0)` sets the bit at index 0 in `100` (result: `101` or 5). - * - `clearBit(7, 1)` clears the bit at index 1 in `111` (result: `101` or 5). - * - `getBit(6, 0)` checks if the least significant bit is set (result: `0`). - * - * Time Complexity: O(1) for all operations. - * - * Author: lukasb1b (https://github.com/lukasb1b) - */ -public final class SingleBitOperations { - private SingleBitOperations() { - } - - /** - * Flips (toggles) the bit at the specified position. - * - * @param num the input number - * @param bit the position of the bit to flip (0-indexed) - * @return the new number after flipping the specified bit - */ - public static int flipBit(final int num, final int bit) { - return num ^ (1 << bit); - } - - /** - * Sets the bit at the specified position to 1. - * - * @param num the input number - * @param bit the position of the bit to set (0-indexed) - * @return the new number after setting the specified bit to 1 - */ - public static int setBit(final int num, final int bit) { - return num | (1 << bit); - } - - /** - * Clears the bit at the specified position (sets it to 0). - * - * @param num the input number - * @param bit the position of the bit to clear (0-indexed) - * @return the new number after clearing the specified bit - */ - public static int clearBit(final int num, final int bit) { - return num & ~(1 << bit); - } - - /** - * Gets the bit value (0 or 1) at the specified position. - * - * @param num the input number - * @param bit the position of the bit to retrieve (0-indexed) - * @return 1 if the bit is set, 0 otherwise - */ - public static int getBit(final int num, final int bit) { - return (num >> bit) & 1; - } -} +package com.thealgorithms.bitmanipulation; + +/** + * A utility class for performing single-bit operations on integers. + * These operations include flipping, setting, clearing, and getting + * individual bits at specified positions. + * + * Bit positions are zero-indexed (i.e., the least significant bit is at position 0). + * These methods leverage bitwise operations for optimal performance. + * + * Examples: + * - `flipBit(3, 1)` flips the bit at index 1 in binary `11` (result: `1`). + * - `setBit(4, 0)` sets the bit at index 0 in `100` (result: `101` or 5). + * - `clearBit(7, 1)` clears the bit at index 1 in `111` (result: `101` or 5). + * - `getBit(6, 0)` checks if the least significant bit is set (result: `0`). + * + * Time Complexity: O(1) for all operations. + * + * Author: lukasb1b (https://github.com/lukasb1b) + */ +public final class SingleBitOperations { + private SingleBitOperations() { + } + + /** + * Flips (toggles) the bit at the specified position. + * + * @param num the input number + * @param bit the position of the bit to flip (0-indexed) + * @return the new number after flipping the specified bit + */ + public static int flipBit(final int num, final int bit) { + return num ^ (1 << bit); + } + + /** + * Sets the bit at the specified position to 1. + * + * @param num the input number + * @param bit the position of the bit to set (0-indexed) + * @return the new number after setting the specified bit to 1 + */ + public static int setBit(final int num, final int bit) { + return num | (1 << bit); + } + + /** + * Clears the bit at the specified position (sets it to 0). + * + * @param num the input number + * @param bit the position of the bit to clear (0-indexed) + * @return the new number after clearing the specified bit + */ + public static int clearBit(final int num, final int bit) { + return num & ~(1 << bit); + } + + /** + * Gets the bit value (0 or 1) at the specified position. + * + * @param num the input number + * @param bit the position of the bit to retrieve (0-indexed) + * @return 1 if the bit is set, 0 otherwise + */ + public static int getBit(final int num, final int bit) { + return (num >> bit) & 1; + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/AtbashCipher.java b/src/main/java/com/thealgorithms/ciphers/AtbashCipher.java index 9169aa82bd75..7e7baaa6f7d5 100644 --- a/src/main/java/com/thealgorithms/ciphers/AtbashCipher.java +++ b/src/main/java/com/thealgorithms/ciphers/AtbashCipher.java @@ -1,101 +1,101 @@ -package com.thealgorithms.ciphers; - -/** - * The Atbash cipher is a classic substitution cipher that substitutes each letter - * with its opposite letter in the alphabet. - * - * For example: - * - 'A' becomes 'Z', 'B' becomes 'Y', 'C' becomes 'X', and so on. - * - Similarly, 'a' becomes 'z', 'b' becomes 'y', and so on. - * - * The cipher works identically for both uppercase and lowercase letters. - * Non-alphabetical characters remain unchanged in the output. - * - * This cipher is symmetric, meaning that applying the cipher twice will return - * the original text. Therefore, the same function is used for both encryption and decryption. - * - *

Usage Example:

- *
- * AtbashCipher cipher = new AtbashCipher("Hello World!");
- * String encrypted = cipher.convert(); // Output: "Svool Dliow!"
- * 
- * - * @author Krounosity - * @see Atbash Cipher (Wikipedia) - */ -public class AtbashCipher { - - private String toConvert; - - public AtbashCipher() { - } - - /** - * Constructor with a string parameter. - * - * @param str The string to be converted using the Atbash cipher - */ - public AtbashCipher(String str) { - this.toConvert = str; - } - - /** - * Returns the current string set for conversion. - * - * @return The string to be converted - */ - public String getString() { - return toConvert; - } - - /** - * Sets the string to be converted using the Atbash cipher. - * - * @param str The new string to convert - */ - public void setString(String str) { - this.toConvert = str; - } - - /** - * Checks if a character is uppercase. - * - * @param ch The character to check - * @return {@code true} if the character is uppercase, {@code false} otherwise - */ - private boolean isCapital(char ch) { - return ch >= 'A' && ch <= 'Z'; - } - - /** - * Checks if a character is lowercase. - * - * @param ch The character to check - * @return {@code true} if the character is lowercase, {@code false} otherwise - */ - private boolean isSmall(char ch) { - return ch >= 'a' && ch <= 'z'; - } - - /** - * Converts the input string using the Atbash cipher. - * Alphabetic characters are substituted with their opposite in the alphabet, - * while non-alphabetic characters remain unchanged. - * - * @return The converted string after applying the Atbash cipher - */ - public String convert() { - StringBuilder convertedString = new StringBuilder(); - - for (char ch : toConvert.toCharArray()) { - if (isSmall(ch)) { - convertedString.append((char) ('z' - (ch - 'a'))); - } else if (isCapital(ch)) { - convertedString.append((char) ('Z' - (ch - 'A'))); - } else { - convertedString.append(ch); - } - } - return convertedString.toString(); - } -} +package com.thealgorithms.ciphers; + +/** + * The Atbash cipher is a classic substitution cipher that substitutes each letter + * with its opposite letter in the alphabet. + * + * For example: + * - 'A' becomes 'Z', 'B' becomes 'Y', 'C' becomes 'X', and so on. + * - Similarly, 'a' becomes 'z', 'b' becomes 'y', and so on. + * + * The cipher works identically for both uppercase and lowercase letters. + * Non-alphabetical characters remain unchanged in the output. + * + * This cipher is symmetric, meaning that applying the cipher twice will return + * the original text. Therefore, the same function is used for both encryption and decryption. + * + *

Usage Example:

+ *
+ * AtbashCipher cipher = new AtbashCipher("Hello World!");
+ * String encrypted = cipher.convert(); // Output: "Svool Dliow!"
+ * 
+ * + * @author Krounosity + * @see Atbash Cipher (Wikipedia) + */ +public class AtbashCipher { + + private String toConvert; + + public AtbashCipher() { + } + + /** + * Constructor with a string parameter. + * + * @param str The string to be converted using the Atbash cipher + */ + public AtbashCipher(String str) { + this.toConvert = str; + } + + /** + * Returns the current string set for conversion. + * + * @return The string to be converted + */ + public String getString() { + return toConvert; + } + + /** + * Sets the string to be converted using the Atbash cipher. + * + * @param str The new string to convert + */ + public void setString(String str) { + this.toConvert = str; + } + + /** + * Checks if a character is uppercase. + * + * @param ch The character to check + * @return {@code true} if the character is uppercase, {@code false} otherwise + */ + private boolean isCapital(char ch) { + return ch >= 'A' && ch <= 'Z'; + } + + /** + * Checks if a character is lowercase. + * + * @param ch The character to check + * @return {@code true} if the character is lowercase, {@code false} otherwise + */ + private boolean isSmall(char ch) { + return ch >= 'a' && ch <= 'z'; + } + + /** + * Converts the input string using the Atbash cipher. + * Alphabetic characters are substituted with their opposite in the alphabet, + * while non-alphabetic characters remain unchanged. + * + * @return The converted string after applying the Atbash cipher + */ + public String convert() { + StringBuilder convertedString = new StringBuilder(); + + for (char ch : toConvert.toCharArray()) { + if (isSmall(ch)) { + convertedString.append((char) ('z' - (ch - 'a'))); + } else if (isCapital(ch)) { + convertedString.append((char) ('Z' - (ch - 'A'))); + } else { + convertedString.append(ch); + } + } + return convertedString.toString(); + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/PermutationCipher.java b/src/main/java/com/thealgorithms/ciphers/PermutationCipher.java index ce443545db1d..26497f0f9955 100644 --- a/src/main/java/com/thealgorithms/ciphers/PermutationCipher.java +++ b/src/main/java/com/thealgorithms/ciphers/PermutationCipher.java @@ -1,194 +1,194 @@ -package com.thealgorithms.ciphers; - -import java.util.HashSet; -import java.util.Set; - -/** - * A Java implementation of Permutation Cipher. - * It is a type of transposition cipher in which the plaintext is divided into blocks - * and the characters within each block are rearranged according to a fixed permutation key. - * - * For example, with key {3, 1, 2} and plaintext "HELLO", the text is divided into blocks - * of 3 characters: "HEL" and "LO" (with padding). The characters are then rearranged - * according to the key positions. - * - * @author GitHub Copilot - */ -public class PermutationCipher { - - private static final char PADDING_CHAR = 'X'; - - /** - * Encrypts the given plaintext using the permutation cipher with the specified key. - * - * @param plaintext the text to encrypt - * @param key the permutation key (array of integers representing positions) - * @return the encrypted text - * @throws IllegalArgumentException if the key is invalid - */ - public String encrypt(String plaintext, int[] key) { - validateKey(key); - - if (plaintext == null || plaintext.isEmpty()) { - return plaintext; - } - - // Remove spaces and convert to uppercase for consistent processing - String cleanText = plaintext.replaceAll("\\s+", "").toUpperCase(); - - // Pad the text to make it divisible by key length - String paddedText = padText(cleanText, key.length); - - StringBuilder encrypted = new StringBuilder(); - - // Process text in blocks of key length - for (int i = 0; i < paddedText.length(); i += key.length) { - String block = paddedText.substring(i, Math.min(i + key.length, paddedText.length())); - encrypted.append(permuteBlock(block, key)); - } - - return encrypted.toString(); - } - - /** - * Decrypts the given ciphertext using the permutation cipher with the specified key. - * - * @param ciphertext the text to decrypt - * @param key the permutation key (array of integers representing positions) - * @return the decrypted text - * @throws IllegalArgumentException if the key is invalid - */ - public String decrypt(String ciphertext, int[] key) { - validateKey(key); - - if (ciphertext == null || ciphertext.isEmpty()) { - return ciphertext; - } - - // Create the inverse permutation - int[] inverseKey = createInverseKey(key); - - StringBuilder decrypted = new StringBuilder(); - - // Process text in blocks of key length - for (int i = 0; i < ciphertext.length(); i += key.length) { - String block = ciphertext.substring(i, Math.min(i + key.length, ciphertext.length())); - decrypted.append(permuteBlock(block, inverseKey)); - } - - // Remove padding characters from the end - return removePadding(decrypted.toString()); - } - /** - * Validates that the permutation key is valid. - * A valid key must contain all integers from 1 to n exactly once, where n is the key length. - * - * @param key the permutation key to validate - * @throws IllegalArgumentException if the key is invalid - */ - private void validateKey(int[] key) { - if (key == null || key.length == 0) { - throw new IllegalArgumentException("Key cannot be null or empty"); - } - - Set keySet = new HashSet<>(); - for (int position : key) { - if (position < 1 || position > key.length) { - throw new IllegalArgumentException("Key must contain integers from 1 to " + key.length); - } - if (!keySet.add(position)) { - throw new IllegalArgumentException("Key must contain each position exactly once"); - } - } - } - - /** - * Pads the text with padding characters to make its length divisible by the block size. - * - * @param text the text to pad - * @param blockSize the size of each block - * @return the padded text - */ - private String padText(String text, int blockSize) { - int remainder = text.length() % blockSize; - if (remainder == 0) { - return text; - } - - int paddingNeeded = blockSize - remainder; - StringBuilder padded = new StringBuilder(text); - for (int i = 0; i < paddingNeeded; i++) { - padded.append(PADDING_CHAR); - } - - return padded.toString(); - } - /** - * Applies the permutation to a single block of text. - * - * @param block the block to permute - * @param key the permutation key - * @return the permuted block - */ - private String permuteBlock(String block, int[] key) { - if (block.length() != key.length) { - // Handle case where block is shorter than key (shouldn't happen with proper padding) - block = padText(block, key.length); - } - - char[] result = new char[key.length]; - char[] blockChars = block.toCharArray(); - - for (int i = 0; i < key.length; i++) { - // Key positions are 1-based, so subtract 1 for 0-based array indexing - result[i] = blockChars[key[i] - 1]; - } - - return new String(result); - } - - /** - * Creates the inverse permutation key for decryption. - * - * @param key the original permutation key - * @return the inverse key - */ - private int[] createInverseKey(int[] key) { - int[] inverse = new int[key.length]; - - for (int i = 0; i < key.length; i++) { - // The inverse key maps each position to where it should go - inverse[key[i] - 1] = i + 1; - } - - return inverse; - } - - /** - * Removes padding characters from the end of the decrypted text. - * - * @param text the text to remove padding from - * @return the text without padding - */ - private String removePadding(String text) { - if (text.isEmpty()) { - return text; - } - - int i = text.length() - 1; - while (i >= 0 && text.charAt(i) == PADDING_CHAR) { - i--; - } - - return text.substring(0, i + 1); - } - - /** - * Gets the padding character used by this cipher. - * - * @return the padding character - */ - public char getPaddingChar() { - return PADDING_CHAR; - } -} +package com.thealgorithms.ciphers; + +import java.util.HashSet; +import java.util.Set; + +/** + * A Java implementation of Permutation Cipher. + * It is a type of transposition cipher in which the plaintext is divided into blocks + * and the characters within each block are rearranged according to a fixed permutation key. + * + * For example, with key {3, 1, 2} and plaintext "HELLO", the text is divided into blocks + * of 3 characters: "HEL" and "LO" (with padding). The characters are then rearranged + * according to the key positions. + * + * @author GitHub Copilot + */ +public class PermutationCipher { + + private static final char PADDING_CHAR = 'X'; + + /** + * Encrypts the given plaintext using the permutation cipher with the specified key. + * + * @param plaintext the text to encrypt + * @param key the permutation key (array of integers representing positions) + * @return the encrypted text + * @throws IllegalArgumentException if the key is invalid + */ + public String encrypt(String plaintext, int[] key) { + validateKey(key); + + if (plaintext == null || plaintext.isEmpty()) { + return plaintext; + } + + // Remove spaces and convert to uppercase for consistent processing + String cleanText = plaintext.replaceAll("\\s+", "").toUpperCase(); + + // Pad the text to make it divisible by key length + String paddedText = padText(cleanText, key.length); + + StringBuilder encrypted = new StringBuilder(); + + // Process text in blocks of key length + for (int i = 0; i < paddedText.length(); i += key.length) { + String block = paddedText.substring(i, Math.min(i + key.length, paddedText.length())); + encrypted.append(permuteBlock(block, key)); + } + + return encrypted.toString(); + } + + /** + * Decrypts the given ciphertext using the permutation cipher with the specified key. + * + * @param ciphertext the text to decrypt + * @param key the permutation key (array of integers representing positions) + * @return the decrypted text + * @throws IllegalArgumentException if the key is invalid + */ + public String decrypt(String ciphertext, int[] key) { + validateKey(key); + + if (ciphertext == null || ciphertext.isEmpty()) { + return ciphertext; + } + + // Create the inverse permutation + int[] inverseKey = createInverseKey(key); + + StringBuilder decrypted = new StringBuilder(); + + // Process text in blocks of key length + for (int i = 0; i < ciphertext.length(); i += key.length) { + String block = ciphertext.substring(i, Math.min(i + key.length, ciphertext.length())); + decrypted.append(permuteBlock(block, inverseKey)); + } + + // Remove padding characters from the end + return removePadding(decrypted.toString()); + } + /** + * Validates that the permutation key is valid. + * A valid key must contain all integers from 1 to n exactly once, where n is the key length. + * + * @param key the permutation key to validate + * @throws IllegalArgumentException if the key is invalid + */ + private void validateKey(int[] key) { + if (key == null || key.length == 0) { + throw new IllegalArgumentException("Key cannot be null or empty"); + } + + Set keySet = new HashSet<>(); + for (int position : key) { + if (position < 1 || position > key.length) { + throw new IllegalArgumentException("Key must contain integers from 1 to " + key.length); + } + if (!keySet.add(position)) { + throw new IllegalArgumentException("Key must contain each position exactly once"); + } + } + } + + /** + * Pads the text with padding characters to make its length divisible by the block size. + * + * @param text the text to pad + * @param blockSize the size of each block + * @return the padded text + */ + private String padText(String text, int blockSize) { + int remainder = text.length() % blockSize; + if (remainder == 0) { + return text; + } + + int paddingNeeded = blockSize - remainder; + StringBuilder padded = new StringBuilder(text); + for (int i = 0; i < paddingNeeded; i++) { + padded.append(PADDING_CHAR); + } + + return padded.toString(); + } + /** + * Applies the permutation to a single block of text. + * + * @param block the block to permute + * @param key the permutation key + * @return the permuted block + */ + private String permuteBlock(String block, int[] key) { + if (block.length() != key.length) { + // Handle case where block is shorter than key (shouldn't happen with proper padding) + block = padText(block, key.length); + } + + char[] result = new char[key.length]; + char[] blockChars = block.toCharArray(); + + for (int i = 0; i < key.length; i++) { + // Key positions are 1-based, so subtract 1 for 0-based array indexing + result[i] = blockChars[key[i] - 1]; + } + + return new String(result); + } + + /** + * Creates the inverse permutation key for decryption. + * + * @param key the original permutation key + * @return the inverse key + */ + private int[] createInverseKey(int[] key) { + int[] inverse = new int[key.length]; + + for (int i = 0; i < key.length; i++) { + // The inverse key maps each position to where it should go + inverse[key[i] - 1] = i + 1; + } + + return inverse; + } + + /** + * Removes padding characters from the end of the decrypted text. + * + * @param text the text to remove padding from + * @return the text without padding + */ + private String removePadding(String text) { + if (text.isEmpty()) { + return text; + } + + int i = text.length() - 1; + while (i >= 0 && text.charAt(i) == PADDING_CHAR) { + i--; + } + + return text.substring(0, i + 1); + } + + /** + * Gets the padding character used by this cipher. + * + * @return the padding character + */ + public char getPaddingChar() { + return PADDING_CHAR; + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java b/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java index f81252980468..2ccdc866ad4c 100644 --- a/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java +++ b/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java @@ -1,147 +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(); - } -} +package com.thealgorithms.ciphers; + +import java.util.Arrays; + +/** + * The rail fence cipher (also called a zigzag cipher) is a classical type of transposition cipher. + * It derives its name from the manner in which encryption is performed, in analogy to a fence built with horizontal rails. + * https://en.wikipedia.org/wiki/Rail_fence_cipher + * @author https://github.com/Krounosity + */ + +public class RailFenceCipher { + + // Encrypts the input string using the rail fence cipher method with the given number of rails. + public String encrypt(String str, int rails) { + + // Base case of single rail or rails are more than the number of characters in the string + if (rails == 1 || rails >= str.length()) { + return str; + } + + // Boolean flag to determine if the movement is downward or upward in the rail matrix. + boolean down = true; + // Create a 2D array to represent the rails (rows) and the length of the string (columns). + char[][] strRail = new char[rails][str.length()]; + + // Initialize all positions in the rail matrix with a placeholder character ('\n'). + for (int i = 0; i < rails; i++) { + Arrays.fill(strRail[i], '\n'); + } + + int row = 0; // Start at the first row + int col = 0; // Start at the first column + + int i = 0; + + // Fill the rail matrix with characters from the string based on the rail pattern. + while (col < str.length()) { + // Change direction to down when at the first row. + if (row == 0) { + down = true; + } + // Change direction to up when at the last row. + else if (row == rails - 1) { + down = false; + } + + // Place the character in the current position of the rail matrix. + strRail[row][col] = str.charAt(i); + col++; // Move to the next column. + // Move to the next row based on the direction. + if (down) { + row++; + } else { + row--; + } + + i++; + } + + // Construct the encrypted string by reading characters row by row. + StringBuilder encryptedString = new StringBuilder(); + for (char[] chRow : strRail) { + for (char ch : chRow) { + if (ch != '\n') { + encryptedString.append(ch); + } + } + } + return encryptedString.toString(); + } + // Decrypts the input string using the rail fence cipher method with the given number of rails. + public String decrypt(String str, int rails) { + + // Base case of single rail or rails are more than the number of characters in the string + if (rails == 1 || rails >= str.length()) { + return str; + } + // Boolean flag to determine if the movement is downward or upward in the rail matrix. + boolean down = true; + + // Create a 2D array to represent the rails (rows) and the length of the string (columns). + char[][] strRail = new char[rails][str.length()]; + + int row = 0; // Start at the first row + int col = 0; // Start at the first column + + // Mark the pattern on the rail matrix using '*'. + while (col < str.length()) { + // Change direction to down when at the first row. + if (row == 0) { + down = true; + } + // Change direction to up when at the last row. + else if (row == rails - 1) { + down = false; + } + + // Mark the current position in the rail matrix. + strRail[row][col] = '*'; + col++; // Move to the next column. + // Move to the next row based on the direction. + if (down) { + row++; + } else { + row--; + } + } + + int index = 0; // Index to track characters from the input string. + // Fill the rail matrix with characters from the input string based on the marked pattern. + for (int i = 0; i < rails; i++) { + for (int j = 0; j < str.length(); j++) { + if (strRail[i][j] == '*') { + strRail[i][j] = str.charAt(index++); + } + } + } + + // Construct the decrypted string by following the zigzag pattern. + StringBuilder decryptedString = new StringBuilder(); + row = 0; // Reset to the first row + col = 0; // Reset to the first column + + while (col < str.length()) { + // Change direction to down when at the first row. + if (row == 0) { + down = true; + } + // Change direction to up when at the last row. + else if (row == rails - 1) { + down = false; + } + // Append the character from the rail matrix to the decrypted string. + decryptedString.append(strRail[row][col]); + col++; // Move to the next column. + // Move to the next row based on the direction. + if (down) { + row++; + } else { + row--; + } + } + + return decryptedString.toString(); + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionBySize.java b/src/main/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionBySize.java index 71951f67dfc8..44665c548e4d 100644 --- a/src/main/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionBySize.java +++ b/src/main/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionBySize.java @@ -1,83 +1,83 @@ -package com.thealgorithms.datastructures.disjointsetunion; - -/** - * Disjoint Set Union (DSU) with Union by Size. - * This data structure tracks a set of elements partitioned into disjoint (non-overlapping) subsets. - * It supports two primary operations efficiently: - * - *
    - *
  • Find: Determine which subset a particular element belongs to.
  • - *
  • Union: Merge two subsets into a single subset using union by size.
  • - *
- * - * Union by size always attaches the smaller tree under the root of the larger tree. - * This helps keep the tree shallow, improving the efficiency of find operations. - * - * @see Disjoint Set Union (Wikipedia) - */ -public class DisjointSetUnionBySize { - /** - * Node class for DSU by size. - * Each node keeps track of its parent and the size of the set it represents. - */ - public static class Node { - public T value; - public Node parent; - public int size; // size of the set - - public Node(T value) { - this.value = value; - this.parent = this; - this.size = 1; // initially, the set size is 1 - } - } - - /** - * Creates a new disjoint set containing the single specified element. - * @param value the element to be placed in a new singleton set - * @return a node representing the new set - */ - public Node makeSet(final T value) { - return new Node<>(value); - } - - /** - * Finds and returns the representative (root) of the set containing the given node. - * This method applies path compression to flatten the tree structure for future efficiency. - * @param node the node whose set representative is to be found - * @return the representative (root) node of the set - */ - public Node findSet(Node node) { - if (node != node.parent) { - node.parent = findSet(node.parent); // path compression - } - return node.parent; - } - - /** - * Merges the sets containing the two given nodes using union by size. - * The root of the smaller set is attached to the root of the larger set. - * @param x a node in the first set - * @param y a node in the second set - */ - public void unionSets(Node x, Node y) { - Node rootX = findSet(x); - Node rootY = findSet(y); - - if (rootX == rootY) { - return; // They are already in the same set - } - // Union by size: attach smaller tree under the larger one - if (rootX.size < rootY.size) { - rootX.parent = rootY; - rootY.size += rootX.size; // update size - } else { - rootY.parent = rootX; - rootX.size += rootY.size; // update size - } - } -} -// This implementation uses union by size instead of union by rank. -// The size field tracks the number of elements in each set. -// When two sets are merged, the smaller set is always attached to the larger set's root. -// This helps keep the tree shallow and improves the efficiency of find operations. +package com.thealgorithms.datastructures.disjointsetunion; + +/** + * Disjoint Set Union (DSU) with Union by Size. + * This data structure tracks a set of elements partitioned into disjoint (non-overlapping) subsets. + * It supports two primary operations efficiently: + * + *
    + *
  • Find: Determine which subset a particular element belongs to.
  • + *
  • Union: Merge two subsets into a single subset using union by size.
  • + *
+ * + * Union by size always attaches the smaller tree under the root of the larger tree. + * This helps keep the tree shallow, improving the efficiency of find operations. + * + * @see Disjoint Set Union (Wikipedia) + */ +public class DisjointSetUnionBySize { + /** + * Node class for DSU by size. + * Each node keeps track of its parent and the size of the set it represents. + */ + public static class Node { + public T value; + public Node parent; + public int size; // size of the set + + public Node(T value) { + this.value = value; + this.parent = this; + this.size = 1; // initially, the set size is 1 + } + } + + /** + * Creates a new disjoint set containing the single specified element. + * @param value the element to be placed in a new singleton set + * @return a node representing the new set + */ + public Node makeSet(final T value) { + return new Node<>(value); + } + + /** + * Finds and returns the representative (root) of the set containing the given node. + * This method applies path compression to flatten the tree structure for future efficiency. + * @param node the node whose set representative is to be found + * @return the representative (root) node of the set + */ + public Node findSet(Node node) { + if (node != node.parent) { + node.parent = findSet(node.parent); // path compression + } + return node.parent; + } + + /** + * Merges the sets containing the two given nodes using union by size. + * The root of the smaller set is attached to the root of the larger set. + * @param x a node in the first set + * @param y a node in the second set + */ + public void unionSets(Node x, Node y) { + Node rootX = findSet(x); + Node rootY = findSet(y); + + if (rootX == rootY) { + return; // They are already in the same set + } + // Union by size: attach smaller tree under the larger one + if (rootX.size < rootY.size) { + rootX.parent = rootY; + rootY.size += rootX.size; // update size + } else { + rootY.parent = rootX; + rootX.size += rootY.size; // update size + } + } +} +// This implementation uses union by size instead of union by rank. +// The size field tracks the number of elements in each set. +// When two sets are merged, the smaller set is always attached to the larger set's root. +// This helps keep the tree shallow and improves the efficiency of find operations. diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCount.java b/src/main/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCount.java index 8c7ea6179e3f..5d0784796cdb 100755 --- a/src/main/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCount.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCount.java @@ -1,98 +1,98 @@ -package com.thealgorithms.dynamicprogramming; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -/** - * Utility class to find the number of unique subsequences that can be - * produced from a given string. - * - *

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

- * - *

Author: https://github.com/Tuhinm2002

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

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

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

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

- * - * @param st the input string - * @param idx the current index from which to calculate subsequences - * @param dp dynamic programming array used to memoize results - * @return the total number of unique subsequences starting from the - * current index - */ - public static int countSubsequences(String st, int idx, int[] dp) { - - // Base case: when index exceeds the string length - if (idx >= st.length()) { - return 0; - } - - // If result is already calculated, return the memoized value - if (dp[idx] != -1) { - return dp[idx]; - } - - // Set to store characters to avoid duplicates - Set set = new HashSet<>(); - - int res = 0; - - // Iterate over the string starting from current index - for (int j = idx; j < st.length(); j++) { - - // If character is already in the set, skip it - if (set.contains(st.charAt(j))) { - continue; - } - - // Add character to set and recursively calculate subsequences - set.add(st.charAt(j)); - - // 1 for the current subsequence + recursive call for the rest of the string - res = 1 + countSubsequences(st, j + 1, dp) + res; - } - - // Memoize the result - dp[idx] = res; - return dp[idx]; - } -} +package com.thealgorithms.dynamicprogramming; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * Utility class to find the number of unique subsequences that can be + * produced from a given string. + * + *

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

+ * + *

Author: https://github.com/Tuhinm2002

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

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

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

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

+ * + * @param st the input string + * @param idx the current index from which to calculate subsequences + * @param dp dynamic programming array used to memoize results + * @return the total number of unique subsequences starting from the + * current index + */ + public static int countSubsequences(String st, int idx, int[] dp) { + + // Base case: when index exceeds the string length + if (idx >= st.length()) { + return 0; + } + + // If result is already calculated, return the memoized value + if (dp[idx] != -1) { + return dp[idx]; + } + + // Set to store characters to avoid duplicates + Set set = new HashSet<>(); + + int res = 0; + + // Iterate over the string starting from current index + for (int j = idx; j < st.length(); j++) { + + // If character is already in the set, skip it + if (set.contains(st.charAt(j))) { + continue; + } + + // Add character to set and recursively calculate subsequences + set.add(st.charAt(j)); + + // 1 for the current subsequence + recursive call for the rest of the string + res = 1 + countSubsequences(st, j + 1, dp) + res; + } + + // Memoize the result + dp[idx] = res; + return dp[idx]; + } +} diff --git a/src/main/java/com/thealgorithms/others/IterativeFloodFill.java b/src/main/java/com/thealgorithms/others/IterativeFloodFill.java index 3f685f418a3d..53f370f5991f 100644 --- a/src/main/java/com/thealgorithms/others/IterativeFloodFill.java +++ b/src/main/java/com/thealgorithms/others/IterativeFloodFill.java @@ -1,102 +1,102 @@ -package com.thealgorithms.others; - -import java.util.LinkedList; -import java.util.Queue; - -/** - * Implementation of the Flood Fill algorithm using an iterative BFS (Breadth-First Search) approach. - * - *

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

- * - *

Implementation Features:

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

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

- *

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

- * - * @see Flood Fill Algorithm - GeeksforGeeks - * @see Flood Fill Algorithm - Wikipedia - */ -public final class IterativeFloodFill { - private IterativeFloodFill() { - } - - /** - * Iteratively fill the 2D image with new color - * - * @param image The image to be filled - * @param x The x coordinate at which color is to be filled - * @param y The y coordinate at which color is to be filled - * @param newColor The new color which to be filled in the image - * @param oldColor The old color which is to be replaced in the image - * @see FloodFill BFS - */ - public static void floodFill(final int[][] image, final int x, final int y, final int newColor, final int oldColor) { - if (image.length == 0 || image[0].length == 0 || newColor == oldColor || shouldSkipPixel(image, x, y, oldColor)) { - return; - } - - Queue queue = new LinkedList<>(); - queue.add(new Point(x, y)); - - int[] dx = {0, 0, -1, 1, 1, -1, 1, -1}; - int[] dy = {-1, 1, 0, 0, -1, 1, 1, -1}; - - while (!queue.isEmpty()) { - Point currPoint = queue.poll(); - - if (shouldSkipPixel(image, currPoint.x, currPoint.y, oldColor)) { - continue; - } - - image[currPoint.x][currPoint.y] = newColor; - - for (int i = 0; i < 8; i++) { - int curX = currPoint.x + dx[i]; - int curY = currPoint.y + dy[i]; - - if (!shouldSkipPixel(image, curX, curY, oldColor)) { - queue.add(new Point(curX, curY)); - } - } - } - } - - /** - * Represents a point in 2D space with integer coordinates. - */ - private static class Point { - final int x; - final int y; - - Point(final int x, final int y) { - this.x = x; - this.y = y; - } - } - - /** - * Checks if a pixel should be skipped during flood fill operation. - * - * @param image The image to get boundaries - * @param x The x coordinate of pixel to check - * @param y The y coordinate of pixel to check - * @param oldColor The old color which is to be replaced in the image - * @return {@code true} if pixel should be skipped, else {@code false} - */ - private static boolean shouldSkipPixel(final int[][] image, final int x, final int y, final int oldColor) { - - if (x < 0 || x >= image.length || y < 0 || y >= image[0].length || image[x][y] != oldColor) { - return true; - } - - return false; - } -} +package com.thealgorithms.others; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * Implementation of the Flood Fill algorithm using an iterative BFS (Breadth-First Search) approach. + * + *

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

+ * + *

Implementation Features:

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

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

+ *

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

+ * + * @see
Flood Fill Algorithm - GeeksforGeeks + * @see Flood Fill Algorithm - Wikipedia + */ +public final class IterativeFloodFill { + private IterativeFloodFill() { + } + + /** + * Iteratively fill the 2D image with new color + * + * @param image The image to be filled + * @param x The x coordinate at which color is to be filled + * @param y The y coordinate at which color is to be filled + * @param newColor The new color which to be filled in the image + * @param oldColor The old color which is to be replaced in the image + * @see FloodFill BFS + */ + public static void floodFill(final int[][] image, final int x, final int y, final int newColor, final int oldColor) { + if (image.length == 0 || image[0].length == 0 || newColor == oldColor || shouldSkipPixel(image, x, y, oldColor)) { + return; + } + + Queue queue = new LinkedList<>(); + queue.add(new Point(x, y)); + + int[] dx = {0, 0, -1, 1, 1, -1, 1, -1}; + int[] dy = {-1, 1, 0, 0, -1, 1, 1, -1}; + + while (!queue.isEmpty()) { + Point currPoint = queue.poll(); + + if (shouldSkipPixel(image, currPoint.x, currPoint.y, oldColor)) { + continue; + } + + image[currPoint.x][currPoint.y] = newColor; + + for (int i = 0; i < 8; i++) { + int curX = currPoint.x + dx[i]; + int curY = currPoint.y + dy[i]; + + if (!shouldSkipPixel(image, curX, curY, oldColor)) { + queue.add(new Point(curX, curY)); + } + } + } + } + + /** + * Represents a point in 2D space with integer coordinates. + */ + private static class Point { + final int x; + final int y; + + Point(final int x, final int y) { + this.x = x; + this.y = y; + } + } + + /** + * Checks if a pixel should be skipped during flood fill operation. + * + * @param image The image to get boundaries + * @param x The x coordinate of pixel to check + * @param y The y coordinate of pixel to check + * @param oldColor The old color which is to be replaced in the image + * @return {@code true} if pixel should be skipped, else {@code false} + */ + private static boolean shouldSkipPixel(final int[][] image, final int x, final int y, final int oldColor) { + + if (x < 0 || x >= image.length || y < 0 || y >= image[0].length || image[x][y] != oldColor) { + return true; + } + + return false; + } +} diff --git a/src/main/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrix.java b/src/main/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrix.java index b53c7e5256ca..048ddd9bdaa8 100644 --- a/src/main/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrix.java +++ b/src/main/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrix.java @@ -1,32 +1,32 @@ -package com.thealgorithms.searches; - -public class SearchInARowAndColWiseSortedMatrix { - /** - * Search a key in row and column wise sorted matrix - * - * @param matrix matrix to be searched - * @param value Key being searched for - * @author Sadiul Hakim : https://github.com/sadiul-hakim - */ - public int[] search(int[][] matrix, int value) { - int n = matrix.length; - // This variable iterates over rows - int i = 0; - // This variable iterates over columns - int j = n - 1; - int[] result = {-1, -1}; - while (i < n && j >= 0) { - if (matrix[i][j] == value) { - result[0] = i; - result[1] = j; - return result; - } - if (value > matrix[i][j]) { - i++; - } else { - j--; - } - } - return result; - } -} +package com.thealgorithms.searches; + +public class SearchInARowAndColWiseSortedMatrix { + /** + * Search a key in row and column wise sorted matrix + * + * @param matrix matrix to be searched + * @param value Key being searched for + * @author Sadiul Hakim : https://github.com/sadiul-hakim + */ + public int[] search(int[][] matrix, int value) { + int n = matrix.length; + // This variable iterates over rows + int i = 0; + // This variable iterates over columns + int j = n - 1; + int[] result = {-1, -1}; + while (i < n && j >= 0) { + if (matrix[i][j] == value) { + result[0] = i; + result[1] = j; + return result; + } + if (value > matrix[i][j]) { + i++; + } else { + j--; + } + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/strings/AhoCorasick.java b/src/main/java/com/thealgorithms/strings/AhoCorasick.java index a68d0823a00d..b01cc1f01335 100644 --- a/src/main/java/com/thealgorithms/strings/AhoCorasick.java +++ b/src/main/java/com/thealgorithms/strings/AhoCorasick.java @@ -1,246 +1,246 @@ -/* - * Aho-Corasick String Matching Algorithm Implementation - * - * This code implements the Aho-Corasick algorithm, which is used for efficient - * string matching in a given text. It can find multiple patterns simultaneously - * and records their positions in the text. - * - * Author: Prabhat-Kumar-42 - * GitHub: https://github.com/Prabhat-Kumar-42 - */ - -package com.thealgorithms.strings; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Queue; - -public final class AhoCorasick { - private AhoCorasick() { - } - - // Trie Node Class - private static class Node { - // Represents a character in the trie - private final Map child = new HashMap<>(); // Child nodes of the current node - private Node suffixLink; // Suffix link to another node in the trie - private Node outputLink; // Output link to another node in the trie - private int patternInd; // Index of the pattern that ends at this node - - Node() { - this.suffixLink = null; - this.outputLink = null; - this.patternInd = -1; - } - - public Map getChild() { - return child; - } - - public Node getSuffixLink() { - return suffixLink; - } - - public void setSuffixLink(final Node suffixLink) { - this.suffixLink = suffixLink; - } - - public Node getOutputLink() { - return outputLink; - } - - public void setOutputLink(final Node outputLink) { - this.outputLink = outputLink; - } - - public int getPatternInd() { - return patternInd; - } - - public void setPatternInd(final int patternInd) { - this.patternInd = patternInd; - } - } - - // Trie Class - public static class Trie { - - private Node root = null; // Root node of the trie - private final String[] patterns; // patterns according to which Trie is constructed - - public Trie(final String[] patterns) { - root = new Node(); // Initialize the root of the trie - this.patterns = patterns; - buildTrie(); - buildSuffixAndOutputLinks(); - } - - // builds AhoCorasick Trie - private void buildTrie() { - - // Loop through each input pattern and building Trie - for (int i = 0; i < patterns.length; i++) { - Node curr = root; // Start at the root of the trie for each pattern - - // Loop through each character in the current pattern - for (int j = 0; j < patterns[i].length(); j++) { - char c = patterns[i].charAt(j); // Get the current character - - // Check if the current node has a child for the current character - if (curr.getChild().containsKey(c)) { - curr = curr.getChild().get(c); // Update the current node to the child node - } else { - // If no child node exists, create a new one and add it to the current node's children - Node nn = new Node(); - curr.getChild().put(c, nn); - curr = nn; // Update the current node to the new child node - } - } - curr.setPatternInd(i); // Store the index of the pattern in the current leaf node - } - } - - private void initializeSuffixLinksForChildNodesOfTheRoot(Queue q) { - for (char rc : root.getChild().keySet()) { - Node childNode = root.getChild().get(rc); - q.add(childNode); // Add child node to the queue - childNode.setSuffixLink(root); // Set suffix link to the root - } - } - - private void buildSuffixAndOutputLinks() { - root.setSuffixLink(root); // Initialize the suffix link of the root to itself - Queue q = new LinkedList<>(); // Initialize a queue for BFS traversal - - initializeSuffixLinksForChildNodesOfTheRoot(q); - - while (!q.isEmpty()) { - Node currentState = q.poll(); // Get the current node for processing - - // Iterate through child nodes of the current node - for (char cc : currentState.getChild().keySet()) { - Node currentChild = currentState.getChild().get(cc); // Get the child node - Node parentSuffix = currentState.getSuffixLink(); // Get the parent's suffix link - - // Calculate the suffix link for the child based on the parent's suffix link - while (!parentSuffix.getChild().containsKey(cc) && parentSuffix != root) { - parentSuffix = parentSuffix.getSuffixLink(); - } - - // Set the calculated suffix link or default to root - if (parentSuffix.getChild().containsKey(cc)) { - currentChild.setSuffixLink(parentSuffix.getChild().get(cc)); - } else { - currentChild.setSuffixLink(root); - } - - q.add(currentChild); // Add the child node to the queue for further processing - } - - // Establish output links for nodes to efficiently identify patterns within patterns - if (currentState.getSuffixLink().getPatternInd() >= 0) { - currentState.setOutputLink(currentState.getSuffixLink()); - } else { - currentState.setOutputLink(currentState.getSuffixLink().getOutputLink()); - } - } - } - - private List> initializePositionByStringIndexValue() { - List> positionByStringIndexValue = new ArrayList<>(patterns.length); // Stores positions where patterns are found in the text - for (int i = 0; i < patterns.length; i++) { - positionByStringIndexValue.add(new ArrayList<>()); - } - return positionByStringIndexValue; - } - - // Searches for patterns in the input text and records their positions - public List> searchIn(final String text) { - var positionByStringIndexValue = initializePositionByStringIndexValue(); // Initialize a list to store positions of the current pattern - Node parent = root; // Start searching from the root node - - PatternPositionRecorder positionRecorder = new PatternPositionRecorder(positionByStringIndexValue); - - for (int i = 0; i < text.length(); i++) { - char ch = text.charAt(i); // Get the current character in the text - - // Check if the current node has a child for the current character - if (parent.getChild().containsKey(ch)) { - parent = parent.getChild().get(ch); // Update the current node to the child node - positionRecorder.recordPatternPositions(parent, i); // Use the method in PatternPositionRecorder to record positions - } else { - // If no child node exists for the character, backtrack using suffix links - while (parent != root && !parent.getChild().containsKey(ch)) { - parent = parent.getSuffixLink(); - } - if (parent.getChild().containsKey(ch)) { - i--; // Decrement i to reprocess the same character - } - } - } - - setUpStartPoints(positionByStringIndexValue); - return positionByStringIndexValue; - } - - // by default positionByStringIndexValue contains end-points. This function converts those - // endpoints to start points - private void setUpStartPoints(List> positionByStringIndexValue) { - for (int i = 0; i < patterns.length; i++) { - for (int j = 0; j < positionByStringIndexValue.get(i).size(); j++) { - int endpoint = positionByStringIndexValue.get(i).get(j); - positionByStringIndexValue.get(i).set(j, endpoint - patterns[i].length() + 1); - } - } - } - } - - // Class to handle pattern position recording - private record PatternPositionRecorder(List> positionByStringIndexValue) { - // Constructor to initialize the recorder with the position list - - /** - * Records positions for a pattern when it's found in the input text and follows - * output links to record positions of other patterns. - * - * @param parent The current node representing a character in the pattern trie. - * @param currentPosition The current position in the input text. - */ - public void recordPatternPositions(final Node parent, final int currentPosition) { - // Check if the current node represents the end of a pattern - if (parent.getPatternInd() > -1) { - // Add the current position to the list of positions for the found pattern - positionByStringIndexValue.get(parent.getPatternInd()).add(currentPosition); - } - - Node outputLink = parent.getOutputLink(); - // Follow output links to find and record positions of other patterns - while (outputLink != null) { - // Add the current position to the list of positions for the pattern linked by outputLink - positionByStringIndexValue.get(outputLink.getPatternInd()).add(currentPosition); - outputLink = outputLink.getOutputLink(); - } - } - } - - // method to search for patterns in text - public static Map> search(final String text, final String[] patterns) { - final var trie = new Trie(patterns); - final var positionByStringIndexValue = trie.searchIn(text); - return convert(positionByStringIndexValue, patterns); - } - - // method for converting results to a map - private static Map> convert(final List> positionByStringIndexValue, final String[] patterns) { - Map> positionByString = new HashMap<>(); - for (int i = 0; i < patterns.length; i++) { - String pattern = patterns[i]; - List positions = positionByStringIndexValue.get(i); - positionByString.put(pattern, new ArrayList<>(positions)); - } - return positionByString; - } -} +/* + * Aho-Corasick String Matching Algorithm Implementation + * + * This code implements the Aho-Corasick algorithm, which is used for efficient + * string matching in a given text. It can find multiple patterns simultaneously + * and records their positions in the text. + * + * Author: Prabhat-Kumar-42 + * GitHub: https://github.com/Prabhat-Kumar-42 + */ + +package com.thealgorithms.strings; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; + +public final class AhoCorasick { + private AhoCorasick() { + } + + // Trie Node Class + private static class Node { + // Represents a character in the trie + private final Map child = new HashMap<>(); // Child nodes of the current node + private Node suffixLink; // Suffix link to another node in the trie + private Node outputLink; // Output link to another node in the trie + private int patternInd; // Index of the pattern that ends at this node + + Node() { + this.suffixLink = null; + this.outputLink = null; + this.patternInd = -1; + } + + public Map getChild() { + return child; + } + + public Node getSuffixLink() { + return suffixLink; + } + + public void setSuffixLink(final Node suffixLink) { + this.suffixLink = suffixLink; + } + + public Node getOutputLink() { + return outputLink; + } + + public void setOutputLink(final Node outputLink) { + this.outputLink = outputLink; + } + + public int getPatternInd() { + return patternInd; + } + + public void setPatternInd(final int patternInd) { + this.patternInd = patternInd; + } + } + + // Trie Class + public static class Trie { + + private Node root = null; // Root node of the trie + private final String[] patterns; // patterns according to which Trie is constructed + + public Trie(final String[] patterns) { + root = new Node(); // Initialize the root of the trie + this.patterns = patterns; + buildTrie(); + buildSuffixAndOutputLinks(); + } + + // builds AhoCorasick Trie + private void buildTrie() { + + // Loop through each input pattern and building Trie + for (int i = 0; i < patterns.length; i++) { + Node curr = root; // Start at the root of the trie for each pattern + + // Loop through each character in the current pattern + for (int j = 0; j < patterns[i].length(); j++) { + char c = patterns[i].charAt(j); // Get the current character + + // Check if the current node has a child for the current character + if (curr.getChild().containsKey(c)) { + curr = curr.getChild().get(c); // Update the current node to the child node + } else { + // If no child node exists, create a new one and add it to the current node's children + Node nn = new Node(); + curr.getChild().put(c, nn); + curr = nn; // Update the current node to the new child node + } + } + curr.setPatternInd(i); // Store the index of the pattern in the current leaf node + } + } + + private void initializeSuffixLinksForChildNodesOfTheRoot(Queue q) { + for (char rc : root.getChild().keySet()) { + Node childNode = root.getChild().get(rc); + q.add(childNode); // Add child node to the queue + childNode.setSuffixLink(root); // Set suffix link to the root + } + } + + private void buildSuffixAndOutputLinks() { + root.setSuffixLink(root); // Initialize the suffix link of the root to itself + Queue q = new LinkedList<>(); // Initialize a queue for BFS traversal + + initializeSuffixLinksForChildNodesOfTheRoot(q); + + while (!q.isEmpty()) { + Node currentState = q.poll(); // Get the current node for processing + + // Iterate through child nodes of the current node + for (char cc : currentState.getChild().keySet()) { + Node currentChild = currentState.getChild().get(cc); // Get the child node + Node parentSuffix = currentState.getSuffixLink(); // Get the parent's suffix link + + // Calculate the suffix link for the child based on the parent's suffix link + while (!parentSuffix.getChild().containsKey(cc) && parentSuffix != root) { + parentSuffix = parentSuffix.getSuffixLink(); + } + + // Set the calculated suffix link or default to root + if (parentSuffix.getChild().containsKey(cc)) { + currentChild.setSuffixLink(parentSuffix.getChild().get(cc)); + } else { + currentChild.setSuffixLink(root); + } + + q.add(currentChild); // Add the child node to the queue for further processing + } + + // Establish output links for nodes to efficiently identify patterns within patterns + if (currentState.getSuffixLink().getPatternInd() >= 0) { + currentState.setOutputLink(currentState.getSuffixLink()); + } else { + currentState.setOutputLink(currentState.getSuffixLink().getOutputLink()); + } + } + } + + private List> initializePositionByStringIndexValue() { + List> positionByStringIndexValue = new ArrayList<>(patterns.length); // Stores positions where patterns are found in the text + for (int i = 0; i < patterns.length; i++) { + positionByStringIndexValue.add(new ArrayList<>()); + } + return positionByStringIndexValue; + } + + // Searches for patterns in the input text and records their positions + public List> searchIn(final String text) { + var positionByStringIndexValue = initializePositionByStringIndexValue(); // Initialize a list to store positions of the current pattern + Node parent = root; // Start searching from the root node + + PatternPositionRecorder positionRecorder = new PatternPositionRecorder(positionByStringIndexValue); + + for (int i = 0; i < text.length(); i++) { + char ch = text.charAt(i); // Get the current character in the text + + // Check if the current node has a child for the current character + if (parent.getChild().containsKey(ch)) { + parent = parent.getChild().get(ch); // Update the current node to the child node + positionRecorder.recordPatternPositions(parent, i); // Use the method in PatternPositionRecorder to record positions + } else { + // If no child node exists for the character, backtrack using suffix links + while (parent != root && !parent.getChild().containsKey(ch)) { + parent = parent.getSuffixLink(); + } + if (parent.getChild().containsKey(ch)) { + i--; // Decrement i to reprocess the same character + } + } + } + + setUpStartPoints(positionByStringIndexValue); + return positionByStringIndexValue; + } + + // by default positionByStringIndexValue contains end-points. This function converts those + // endpoints to start points + private void setUpStartPoints(List> positionByStringIndexValue) { + for (int i = 0; i < patterns.length; i++) { + for (int j = 0; j < positionByStringIndexValue.get(i).size(); j++) { + int endpoint = positionByStringIndexValue.get(i).get(j); + positionByStringIndexValue.get(i).set(j, endpoint - patterns[i].length() + 1); + } + } + } + } + + // Class to handle pattern position recording + private record PatternPositionRecorder(List> positionByStringIndexValue) { + // Constructor to initialize the recorder with the position list + + /** + * Records positions for a pattern when it's found in the input text and follows + * output links to record positions of other patterns. + * + * @param parent The current node representing a character in the pattern trie. + * @param currentPosition The current position in the input text. + */ + public void recordPatternPositions(final Node parent, final int currentPosition) { + // Check if the current node represents the end of a pattern + if (parent.getPatternInd() > -1) { + // Add the current position to the list of positions for the found pattern + positionByStringIndexValue.get(parent.getPatternInd()).add(currentPosition); + } + + Node outputLink = parent.getOutputLink(); + // Follow output links to find and record positions of other patterns + while (outputLink != null) { + // Add the current position to the list of positions for the pattern linked by outputLink + positionByStringIndexValue.get(outputLink.getPatternInd()).add(currentPosition); + outputLink = outputLink.getOutputLink(); + } + } + } + + // method to search for patterns in text + public static Map> search(final String text, final String[] patterns) { + final var trie = new Trie(patterns); + final var positionByStringIndexValue = trie.searchIn(text); + return convert(positionByStringIndexValue, patterns); + } + + // method for converting results to a map + private static Map> convert(final List> positionByStringIndexValue, final String[] patterns) { + Map> positionByString = new HashMap<>(); + for (int i = 0; i < patterns.length; i++) { + String pattern = patterns[i]; + List positions = positionByStringIndexValue.get(i); + positionByString.put(pattern, new ArrayList<>(positions)); + } + return positionByString; + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/AtbashTest.java b/src/test/java/com/thealgorithms/ciphers/AtbashTest.java index ec34bc26ad72..490dbbc63197 100644 --- a/src/test/java/com/thealgorithms/ciphers/AtbashTest.java +++ b/src/test/java/com/thealgorithms/ciphers/AtbashTest.java @@ -1,43 +1,43 @@ -package com.thealgorithms.ciphers; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.stream.Stream; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -public class AtbashTest { - - @ParameterizedTest - @MethodSource("cipherTestProvider") - public void testAtbashCipher(String input, String expected) { - AtbashCipher cipher = new AtbashCipher(input); - assertEquals(expected, cipher.convert()); - } - - private static Stream cipherTestProvider() { - return Stream.of( - // Basic tests with lowercase and uppercase - Arguments.of("Hello", "Svool"), Arguments.of("WORLD", "DLIOW"), - - // Mixed case with spaces and punctuation - Arguments.of("Hello World!", "Svool Dliow!"), Arguments.of("123 ABC xyz", "123 ZYX cba"), - - // Palindromes and mixed cases - Arguments.of("madam", "nzwzn"), Arguments.of("Palindrome", "Kzormwilnv"), - - // Non-alphabetic characters should remain unchanged - Arguments.of("@cipher 123!", "@xrksvi 123!"), Arguments.of("no-change", "ml-xszmtv"), - - // Empty string and single characters - Arguments.of("", ""), Arguments.of("A", "Z"), Arguments.of("z", "a"), - - // Numbers and symbols - Arguments.of("!@#123", "!@#123"), - - // Full sentence with uppercase, lowercase, symbols, and numbers - Arguments.of("Hello World! 123, @cipher abcDEF ZYX 987 madam zzZ Palindrome!", "Svool Dliow! 123, @xrksvi zyxWVU ABC 987 nzwzn aaA Kzormwilnv!"), - Arguments.of("Svool Dliow! 123, @xrksvi zyxWVU ABC 987 nzwzn aaA Kzormwilnv!", "Hello World! 123, @cipher abcDEF ZYX 987 madam zzZ Palindrome!")); - } -} +package com.thealgorithms.ciphers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class AtbashTest { + + @ParameterizedTest + @MethodSource("cipherTestProvider") + public void testAtbashCipher(String input, String expected) { + AtbashCipher cipher = new AtbashCipher(input); + assertEquals(expected, cipher.convert()); + } + + private static Stream cipherTestProvider() { + return Stream.of( + // Basic tests with lowercase and uppercase + Arguments.of("Hello", "Svool"), Arguments.of("WORLD", "DLIOW"), + + // Mixed case with spaces and punctuation + Arguments.of("Hello World!", "Svool Dliow!"), Arguments.of("123 ABC xyz", "123 ZYX cba"), + + // Palindromes and mixed cases + Arguments.of("madam", "nzwzn"), Arguments.of("Palindrome", "Kzormwilnv"), + + // Non-alphabetic characters should remain unchanged + Arguments.of("@cipher 123!", "@xrksvi 123!"), Arguments.of("no-change", "ml-xszmtv"), + + // Empty string and single characters + Arguments.of("", ""), Arguments.of("A", "Z"), Arguments.of("z", "a"), + + // Numbers and symbols + Arguments.of("!@#123", "!@#123"), + + // Full sentence with uppercase, lowercase, symbols, and numbers + Arguments.of("Hello World! 123, @cipher abcDEF ZYX 987 madam zzZ Palindrome!", "Svool Dliow! 123, @xrksvi zyxWVU ABC 987 nzwzn aaA Kzormwilnv!"), + Arguments.of("Svool Dliow! 123, @xrksvi zyxWVU ABC 987 nzwzn aaA Kzormwilnv!", "Hello World! 123, @cipher abcDEF ZYX 987 madam zzZ Palindrome!")); + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/PermutationCipherTest.java b/src/test/java/com/thealgorithms/ciphers/PermutationCipherTest.java index ecb7455c1ba2..83e8fdc43c88 100644 --- a/src/test/java/com/thealgorithms/ciphers/PermutationCipherTest.java +++ b/src/test/java/com/thealgorithms/ciphers/PermutationCipherTest.java @@ -1,324 +1,324 @@ -package com.thealgorithms.ciphers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Test; - -class PermutationCipherTest { - - private final PermutationCipher cipher = new PermutationCipher(); - - @Test - void testBasicEncryption() { - // given - String plaintext = "HELLO"; - int[] key = {3, 1, 2}; // Move 3rd position to 1st, 1st to 2nd, 2nd to 3rd - - // when - String encrypted = cipher.encrypt(plaintext, key); - - // then - // "HELLO" becomes "HEL" + "LOX" (padded) - // "HEL" with key {3,1,2} becomes "LHE" (L=3rd, H=1st, E=2nd) - // "LOX" with key {3,1,2} becomes "XLO" (X=3rd, L=1st, O=2nd) - assertEquals("LHEXLO", encrypted); - } - - @Test - void testBasicDecryption() { - // given - String ciphertext = "LHEXLO"; - int[] key = {3, 1, 2}; - - // when - String decrypted = cipher.decrypt(ciphertext, key); - - // then - assertEquals("HELLO", decrypted); - } - - @Test - void testEncryptDecryptRoundTrip() { - // given - String plaintext = "THIS IS A TEST MESSAGE"; - int[] key = {4, 2, 1, 3}; - - // when - String encrypted = cipher.encrypt(plaintext, key); - String decrypted = cipher.decrypt(encrypted, key); - - // then - assertEquals("THISISATESTMESSAGE", decrypted); // Spaces are removed during encryption - } - - @Test - void testSingleCharacterKey() { - // given - String plaintext = "ABCDEF"; - int[] key = {1}; // Identity permutation - - // when - String encrypted = cipher.encrypt(plaintext, key); - String decrypted = cipher.decrypt(encrypted, key); - - // then - assertEquals("ABCDEF", encrypted); // Should remain unchanged - assertEquals("ABCDEF", decrypted); - } - - @Test - void testLargerKey() { - // given - String plaintext = "PERMUTATION"; - int[] key = {5, 3, 1, 4, 2}; // 5-character permutation - - // when - String encrypted = cipher.encrypt(plaintext, key); - String decrypted = cipher.decrypt(encrypted, key); - - // then - assertEquals("PERMUTATION", decrypted); - } - - @Test - void testExactBlockSize() { - // given - String plaintext = "ABCDEF"; // Length 6, divisible by key length 3 - int[] key = {2, 3, 1}; - - // when - String encrypted = cipher.encrypt(plaintext, key); - String decrypted = cipher.decrypt(encrypted, key); - - // then - assertEquals("ABCDEF", decrypted); - } - - @Test - void testEmptyString() { - // given - String plaintext = ""; - int[] key = {2, 1, 3}; - - // when - String encrypted = cipher.encrypt(plaintext, key); - String decrypted = cipher.decrypt(encrypted, key); - - // then - assertEquals("", encrypted); - assertEquals("", decrypted); - } - - @Test - void testNullString() { - // given - String plaintext = null; - int[] key = {2, 1, 3}; - - // when - String encrypted = cipher.encrypt(plaintext, key); - String decrypted = cipher.decrypt(encrypted, key); - - // then - assertNull(encrypted); - assertNull(decrypted); - } - - @Test - void testStringWithSpaces() { - // given - String plaintext = "A B C D E F"; - int[] key = {2, 1}; - - // when - String encrypted = cipher.encrypt(plaintext, key); - String decrypted = cipher.decrypt(encrypted, key); - - // then - assertEquals("ABCDEF", decrypted); // Spaces should be removed - } - - @Test - void testLowercaseConversion() { - // given - String plaintext = "hello world"; - int[] key = {3, 1, 2}; - - // when - String encrypted = cipher.encrypt(plaintext, key); - String decrypted = cipher.decrypt(encrypted, key); - - // then - assertEquals("HELLOWORLD", decrypted); // Should be converted to uppercase - } - - @Test - void testInvalidKeyNull() { - // given - String plaintext = "HELLO"; - int[] key = null; - - // when & then - assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key)); - assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key)); - } - - @Test - void testInvalidKeyEmpty() { - // given - String plaintext = "HELLO"; - int[] key = {}; - - // when & then - assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key)); - assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key)); - } - - @Test - void testInvalidKeyOutOfRange() { - // given - String plaintext = "HELLO"; - int[] key = {1, 2, 4}; // 4 is out of range for key length 3 - - // when & then - assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key)); - assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key)); - } - - @Test - void testInvalidKeyZero() { - // given - String plaintext = "HELLO"; - int[] key = {0, 1, 2}; // 0 is invalid (should be 1-based) - - // when & then - assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key)); - assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key)); - } - - @Test - void testInvalidKeyDuplicate() { - // given - String plaintext = "HELLO"; - int[] key = {1, 2, 2}; // Duplicate position - - // when & then - assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key)); - assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key)); - } - - @Test - void testInvalidKeyMissingPosition() { - // given - String plaintext = "HELLO"; - int[] key = {1, 3}; // Missing position 2 - - // when & then - assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key)); - assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key)); - } - - @Test - void testReverseKey() { - // given - String plaintext = "ABCD"; - int[] key = {4, 3, 2, 1}; // Reverse order - - // when - String encrypted = cipher.encrypt(plaintext, key); - String decrypted = cipher.decrypt(encrypted, key); - - // then - assertEquals("DCBA", encrypted); // Should be reversed - assertEquals("ABCD", decrypted); - } - - @Test - void testSpecificExampleFromDescription() { - // given - String plaintext = "HELLO"; - int[] key = {3, 1, 2}; - - // when - String encrypted = cipher.encrypt(plaintext, key); - - // then - // Block 1: "HEL" -> positions {3,1,2} -> "LHE" - // Block 2: "LOX" -> positions {3,1,2} -> "XLO" - assertEquals("LHEXLO", encrypted); - // Verify decryption - String decrypted = cipher.decrypt(encrypted, key); - assertEquals("HELLO", decrypted); - } - - @Test - void testPaddingCharacterGetter() { - // when - char paddingChar = cipher.getPaddingChar(); - - // then - assertEquals('X', paddingChar); - } - - @Test - void testLongText() { - // given - String plaintext = "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG"; - int[] key = {4, 1, 3, 2}; - - // when - String encrypted = cipher.encrypt(plaintext, key); - String decrypted = cipher.decrypt(encrypted, key); - - // then - assertEquals("THEQUICKBROWNFOXJUMPSOVERTHELAZYDOG", decrypted); - } - - @Test - void testIdentityPermutation() { - // given - String plaintext = "IDENTITY"; - int[] key = {1, 2, 3, 4}; // Identity permutation - - // when - String encrypted = cipher.encrypt(plaintext, key); - String decrypted = cipher.decrypt(encrypted, key); - - // then - assertEquals("IDENTITY", encrypted); // Should remain unchanged - assertEquals("IDENTITY", decrypted); - } - - @Test - void testEmptyStringRemovePadding() { - // given - Test to cover line 178 (empty string case in removePadding) - String ciphertext = ""; - int[] key = {2, 1, 3}; - - // when - String decrypted = cipher.decrypt(ciphertext, key); - - // then - assertEquals("", decrypted); // Should return empty string directly - } - - @Test - void testBlockShorterThanKey() { - // given - Test to cover line 139 (block length != key length case) - // This is a defensive case where permuteBlock might receive a block shorter than key - // We can test this by manually creating a scenario with malformed ciphertext - String malformedCiphertext = "AB"; // Length 2, but key length is 3 - int[] key = {3, 1, 2}; // Key length is 3 - - // when - This should trigger the padding logic in permuteBlock during decryption - String decrypted = cipher.decrypt(malformedCiphertext, key); - - // then - The method should handle the short block gracefully - // "AB" gets padded to "ABX", then permuted with inverse key {2,3,1} - // inverse key {2,3,1} means: pos 2→1st, pos 3→2nd, pos 1→3rd = "BXA" - // Padding removal only removes trailing X's, so "BXA" remains as is - assertEquals("BXA", decrypted); - } -} +package com.thealgorithms.ciphers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class PermutationCipherTest { + + private final PermutationCipher cipher = new PermutationCipher(); + + @Test + void testBasicEncryption() { + // given + String plaintext = "HELLO"; + int[] key = {3, 1, 2}; // Move 3rd position to 1st, 1st to 2nd, 2nd to 3rd + + // when + String encrypted = cipher.encrypt(plaintext, key); + + // then + // "HELLO" becomes "HEL" + "LOX" (padded) + // "HEL" with key {3,1,2} becomes "LHE" (L=3rd, H=1st, E=2nd) + // "LOX" with key {3,1,2} becomes "XLO" (X=3rd, L=1st, O=2nd) + assertEquals("LHEXLO", encrypted); + } + + @Test + void testBasicDecryption() { + // given + String ciphertext = "LHEXLO"; + int[] key = {3, 1, 2}; + + // when + String decrypted = cipher.decrypt(ciphertext, key); + + // then + assertEquals("HELLO", decrypted); + } + + @Test + void testEncryptDecryptRoundTrip() { + // given + String plaintext = "THIS IS A TEST MESSAGE"; + int[] key = {4, 2, 1, 3}; + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals("THISISATESTMESSAGE", decrypted); // Spaces are removed during encryption + } + + @Test + void testSingleCharacterKey() { + // given + String plaintext = "ABCDEF"; + int[] key = {1}; // Identity permutation + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals("ABCDEF", encrypted); // Should remain unchanged + assertEquals("ABCDEF", decrypted); + } + + @Test + void testLargerKey() { + // given + String plaintext = "PERMUTATION"; + int[] key = {5, 3, 1, 4, 2}; // 5-character permutation + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals("PERMUTATION", decrypted); + } + + @Test + void testExactBlockSize() { + // given + String plaintext = "ABCDEF"; // Length 6, divisible by key length 3 + int[] key = {2, 3, 1}; + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals("ABCDEF", decrypted); + } + + @Test + void testEmptyString() { + // given + String plaintext = ""; + int[] key = {2, 1, 3}; + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals("", encrypted); + assertEquals("", decrypted); + } + + @Test + void testNullString() { + // given + String plaintext = null; + int[] key = {2, 1, 3}; + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertNull(encrypted); + assertNull(decrypted); + } + + @Test + void testStringWithSpaces() { + // given + String plaintext = "A B C D E F"; + int[] key = {2, 1}; + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals("ABCDEF", decrypted); // Spaces should be removed + } + + @Test + void testLowercaseConversion() { + // given + String plaintext = "hello world"; + int[] key = {3, 1, 2}; + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals("HELLOWORLD", decrypted); // Should be converted to uppercase + } + + @Test + void testInvalidKeyNull() { + // given + String plaintext = "HELLO"; + int[] key = null; + + // when & then + assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key)); + assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key)); + } + + @Test + void testInvalidKeyEmpty() { + // given + String plaintext = "HELLO"; + int[] key = {}; + + // when & then + assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key)); + assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key)); + } + + @Test + void testInvalidKeyOutOfRange() { + // given + String plaintext = "HELLO"; + int[] key = {1, 2, 4}; // 4 is out of range for key length 3 + + // when & then + assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key)); + assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key)); + } + + @Test + void testInvalidKeyZero() { + // given + String plaintext = "HELLO"; + int[] key = {0, 1, 2}; // 0 is invalid (should be 1-based) + + // when & then + assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key)); + assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key)); + } + + @Test + void testInvalidKeyDuplicate() { + // given + String plaintext = "HELLO"; + int[] key = {1, 2, 2}; // Duplicate position + + // when & then + assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key)); + assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key)); + } + + @Test + void testInvalidKeyMissingPosition() { + // given + String plaintext = "HELLO"; + int[] key = {1, 3}; // Missing position 2 + + // when & then + assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key)); + assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key)); + } + + @Test + void testReverseKey() { + // given + String plaintext = "ABCD"; + int[] key = {4, 3, 2, 1}; // Reverse order + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals("DCBA", encrypted); // Should be reversed + assertEquals("ABCD", decrypted); + } + + @Test + void testSpecificExampleFromDescription() { + // given + String plaintext = "HELLO"; + int[] key = {3, 1, 2}; + + // when + String encrypted = cipher.encrypt(plaintext, key); + + // then + // Block 1: "HEL" -> positions {3,1,2} -> "LHE" + // Block 2: "LOX" -> positions {3,1,2} -> "XLO" + assertEquals("LHEXLO", encrypted); + // Verify decryption + String decrypted = cipher.decrypt(encrypted, key); + assertEquals("HELLO", decrypted); + } + + @Test + void testPaddingCharacterGetter() { + // when + char paddingChar = cipher.getPaddingChar(); + + // then + assertEquals('X', paddingChar); + } + + @Test + void testLongText() { + // given + String plaintext = "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG"; + int[] key = {4, 1, 3, 2}; + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals("THEQUICKBROWNFOXJUMPSOVERTHELAZYDOG", decrypted); + } + + @Test + void testIdentityPermutation() { + // given + String plaintext = "IDENTITY"; + int[] key = {1, 2, 3, 4}; // Identity permutation + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals("IDENTITY", encrypted); // Should remain unchanged + assertEquals("IDENTITY", decrypted); + } + + @Test + void testEmptyStringRemovePadding() { + // given - Test to cover line 178 (empty string case in removePadding) + String ciphertext = ""; + int[] key = {2, 1, 3}; + + // when + String decrypted = cipher.decrypt(ciphertext, key); + + // then + assertEquals("", decrypted); // Should return empty string directly + } + + @Test + void testBlockShorterThanKey() { + // given - Test to cover line 139 (block length != key length case) + // This is a defensive case where permuteBlock might receive a block shorter than key + // We can test this by manually creating a scenario with malformed ciphertext + String malformedCiphertext = "AB"; // Length 2, but key length is 3 + int[] key = {3, 1, 2}; // Key length is 3 + + // when - This should trigger the padding logic in permuteBlock during decryption + String decrypted = cipher.decrypt(malformedCiphertext, key); + + // then - The method should handle the short block gracefully + // "AB" gets padded to "ABX", then permuted with inverse key {2,3,1} + // inverse key {2,3,1} means: pos 2→1st, pos 3→2nd, pos 1→3rd = "BXA" + // Padding removal only removes trailing X's, so "BXA" remains as is + assertEquals("BXA", decrypted); + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/RailFenceTest.java b/src/test/java/com/thealgorithms/ciphers/RailFenceTest.java index 2bfa704e3d0b..173c91078491 100644 --- a/src/test/java/com/thealgorithms/ciphers/RailFenceTest.java +++ b/src/test/java/com/thealgorithms/ciphers/RailFenceTest.java @@ -1,62 +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); - } -} +package com.thealgorithms.ciphers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class RailFenceTest { + + @Test + void testEncryption() { + RailFenceCipher cipher = new RailFenceCipher(); + + String input = "We are discovered! Flee at once"; + int rails = 3; + String encrypted = cipher.encrypt(input, rails); + assertEquals("Wrivdlaneaedsoee!Fe toc cr e e", encrypted); + + String singleChar = "A"; + int singleRail = 2; + String encryptedSingleChar = cipher.encrypt(singleChar, singleRail); + assertEquals("A", encryptedSingleChar); + + String shortString = "Hello"; + int moreRails = 10; + String encryptedShortString = cipher.encrypt(shortString, moreRails); + assertEquals("Hello", encryptedShortString); + + String inputSingleRail = "Single line"; + int singleRailOnly = 1; + String encryptedSingleRail = cipher.encrypt(inputSingleRail, singleRailOnly); + assertEquals("Single line", encryptedSingleRail); + } + + @Test + void testDecryption() { + RailFenceCipher cipher = new RailFenceCipher(); + + // Scenario 1: Basic decryption with multiple rails + String encryptedInput = "Wrivdlaneaedsoee!Fe toc cr e e"; + int rails = 3; + String decrypted = cipher.decrypt(encryptedInput, rails); + assertEquals("We are discovered! Flee at once", decrypted); + + // Scenario 2: Single character string decryption + String encryptedSingleChar = "A"; + int singleRail = 2; // More than 1 rail + String decryptedSingleChar = cipher.decrypt(encryptedSingleChar, singleRail); + assertEquals("A", decryptedSingleChar); + + // Scenario 3: String length less than the number of rails + String encryptedShortString = "Hello"; + int moreRails = 10; // More rails than characters + String decryptedShortString = cipher.decrypt(encryptedShortString, moreRails); + assertEquals("Hello", decryptedShortString); + + // Scenario 4: Single rail decryption (output should be the same as input) + String encryptedSingleRail = "Single line"; + int singleRailOnly = 1; + String decryptedSingleRail = cipher.decrypt(encryptedSingleRail, singleRailOnly); + assertEquals("Single line", decryptedSingleRail); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionBySizeTest.java b/src/test/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionBySizeTest.java index 71dade9796dc..a4d98215e259 100644 --- a/src/test/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionBySizeTest.java +++ b/src/test/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionBySizeTest.java @@ -1,146 +1,146 @@ -package com.thealgorithms.datastructures.disjointsetunion; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import org.junit.jupiter.api.Test; - -public class DisjointSetUnionBySizeTest { - - @Test - public void testMakeSet() { - DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); - DisjointSetUnionBySize.Node node = dsu.makeSet(1); - assertNotNull(node); - assertEquals(node, node.parent); - assertEquals(1, node.size); - } - - @Test - public void testUnionFindSet() { - DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); - DisjointSetUnionBySize.Node node1 = dsu.makeSet(1); - DisjointSetUnionBySize.Node node2 = dsu.makeSet(2); - DisjointSetUnionBySize.Node node3 = dsu.makeSet(3); - DisjointSetUnionBySize.Node node4 = dsu.makeSet(4); - - dsu.unionSets(node1, node2); - dsu.unionSets(node3, node2); - dsu.unionSets(node3, node4); - dsu.unionSets(node1, node3); - - DisjointSetUnionBySize.Node root1 = dsu.findSet(node1); - DisjointSetUnionBySize.Node root2 = dsu.findSet(node2); - DisjointSetUnionBySize.Node root3 = dsu.findSet(node3); - DisjointSetUnionBySize.Node root4 = dsu.findSet(node4); - - assertEquals(root1, root2); - assertEquals(root1, root3); - assertEquals(root1, root4); - assertEquals(4, root1.size); - } - - @Test - public void testFindSetOnSingleNode() { - DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); - DisjointSetUnionBySize.Node node = dsu.makeSet("A"); - assertEquals(node, dsu.findSet(node)); - } - - @Test - public void testUnionAlreadyConnectedNodes() { - DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); - DisjointSetUnionBySize.Node node1 = dsu.makeSet(1); - DisjointSetUnionBySize.Node node2 = dsu.makeSet(2); - DisjointSetUnionBySize.Node node3 = dsu.makeSet(3); - - dsu.unionSets(node1, node2); - dsu.unionSets(node2, node3); - - // Union nodes that are already connected - dsu.unionSets(node1, node3); - - // All should have the same root - DisjointSetUnionBySize.Node root = dsu.findSet(node1); - assertEquals(root, dsu.findSet(node2)); - assertEquals(root, dsu.findSet(node3)); - assertEquals(3, root.size); - } - - @Test - public void testMultipleMakeSets() { - DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); - DisjointSetUnionBySize.Node node1 = dsu.makeSet(1); - DisjointSetUnionBySize.Node node2 = dsu.makeSet(2); - DisjointSetUnionBySize.Node node3 = dsu.makeSet(3); - - assertNotEquals(node1, node2); - assertNotEquals(node2, node3); - assertNotEquals(node1, node3); - - assertEquals(node1, node1.parent); - assertEquals(node2, node2.parent); - assertEquals(node3, node3.parent); - assertEquals(1, node1.size); - assertEquals(1, node2.size); - assertEquals(1, node3.size); - } - - @Test - public void testPathCompression() { - DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); - DisjointSetUnionBySize.Node node1 = dsu.makeSet(1); - DisjointSetUnionBySize.Node node2 = dsu.makeSet(2); - DisjointSetUnionBySize.Node node3 = dsu.makeSet(3); - - dsu.unionSets(node1, node2); - dsu.unionSets(node2, node3); - - // After findSet, path compression should update parent to root directly - DisjointSetUnionBySize.Node root = dsu.findSet(node3); - assertEquals(root, node1); - assertEquals(node1, node3.parent); - assertEquals(3, root.size); - } - - @Test - public void testMultipleDisjointSets() { - DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); - DisjointSetUnionBySize.Node node1 = dsu.makeSet(1); - DisjointSetUnionBySize.Node node2 = dsu.makeSet(2); - DisjointSetUnionBySize.Node node3 = dsu.makeSet(3); - DisjointSetUnionBySize.Node node4 = dsu.makeSet(4); - DisjointSetUnionBySize.Node node5 = dsu.makeSet(5); - DisjointSetUnionBySize.Node node6 = dsu.makeSet(6); - - // Create two separate components - dsu.unionSets(node1, node2); - dsu.unionSets(node2, node3); - - dsu.unionSets(node4, node5); - dsu.unionSets(node5, node6); - - // Verify they are separate - assertEquals(dsu.findSet(node1), dsu.findSet(node2)); - assertEquals(dsu.findSet(node2), dsu.findSet(node3)); - assertEquals(dsu.findSet(node4), dsu.findSet(node5)); - assertEquals(dsu.findSet(node5), dsu.findSet(node6)); - - assertNotEquals(dsu.findSet(node1), dsu.findSet(node4)); - assertNotEquals(dsu.findSet(node3), dsu.findSet(node6)); - } - - @Test - public void testEmptyValues() { - DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); - DisjointSetUnionBySize.Node emptyNode = dsu.makeSet(""); - DisjointSetUnionBySize.Node nullNode = dsu.makeSet(null); - - assertEquals(emptyNode, dsu.findSet(emptyNode)); - assertEquals(nullNode, dsu.findSet(nullNode)); - - dsu.unionSets(emptyNode, nullNode); - assertEquals(dsu.findSet(emptyNode), dsu.findSet(nullNode)); - } -} +package com.thealgorithms.datastructures.disjointsetunion; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +public class DisjointSetUnionBySizeTest { + + @Test + public void testMakeSet() { + DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); + DisjointSetUnionBySize.Node node = dsu.makeSet(1); + assertNotNull(node); + assertEquals(node, node.parent); + assertEquals(1, node.size); + } + + @Test + public void testUnionFindSet() { + DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); + DisjointSetUnionBySize.Node node1 = dsu.makeSet(1); + DisjointSetUnionBySize.Node node2 = dsu.makeSet(2); + DisjointSetUnionBySize.Node node3 = dsu.makeSet(3); + DisjointSetUnionBySize.Node node4 = dsu.makeSet(4); + + dsu.unionSets(node1, node2); + dsu.unionSets(node3, node2); + dsu.unionSets(node3, node4); + dsu.unionSets(node1, node3); + + DisjointSetUnionBySize.Node root1 = dsu.findSet(node1); + DisjointSetUnionBySize.Node root2 = dsu.findSet(node2); + DisjointSetUnionBySize.Node root3 = dsu.findSet(node3); + DisjointSetUnionBySize.Node root4 = dsu.findSet(node4); + + assertEquals(root1, root2); + assertEquals(root1, root3); + assertEquals(root1, root4); + assertEquals(4, root1.size); + } + + @Test + public void testFindSetOnSingleNode() { + DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); + DisjointSetUnionBySize.Node node = dsu.makeSet("A"); + assertEquals(node, dsu.findSet(node)); + } + + @Test + public void testUnionAlreadyConnectedNodes() { + DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); + DisjointSetUnionBySize.Node node1 = dsu.makeSet(1); + DisjointSetUnionBySize.Node node2 = dsu.makeSet(2); + DisjointSetUnionBySize.Node node3 = dsu.makeSet(3); + + dsu.unionSets(node1, node2); + dsu.unionSets(node2, node3); + + // Union nodes that are already connected + dsu.unionSets(node1, node3); + + // All should have the same root + DisjointSetUnionBySize.Node root = dsu.findSet(node1); + assertEquals(root, dsu.findSet(node2)); + assertEquals(root, dsu.findSet(node3)); + assertEquals(3, root.size); + } + + @Test + public void testMultipleMakeSets() { + DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); + DisjointSetUnionBySize.Node node1 = dsu.makeSet(1); + DisjointSetUnionBySize.Node node2 = dsu.makeSet(2); + DisjointSetUnionBySize.Node node3 = dsu.makeSet(3); + + assertNotEquals(node1, node2); + assertNotEquals(node2, node3); + assertNotEquals(node1, node3); + + assertEquals(node1, node1.parent); + assertEquals(node2, node2.parent); + assertEquals(node3, node3.parent); + assertEquals(1, node1.size); + assertEquals(1, node2.size); + assertEquals(1, node3.size); + } + + @Test + public void testPathCompression() { + DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); + DisjointSetUnionBySize.Node node1 = dsu.makeSet(1); + DisjointSetUnionBySize.Node node2 = dsu.makeSet(2); + DisjointSetUnionBySize.Node node3 = dsu.makeSet(3); + + dsu.unionSets(node1, node2); + dsu.unionSets(node2, node3); + + // After findSet, path compression should update parent to root directly + DisjointSetUnionBySize.Node root = dsu.findSet(node3); + assertEquals(root, node1); + assertEquals(node1, node3.parent); + assertEquals(3, root.size); + } + + @Test + public void testMultipleDisjointSets() { + DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); + DisjointSetUnionBySize.Node node1 = dsu.makeSet(1); + DisjointSetUnionBySize.Node node2 = dsu.makeSet(2); + DisjointSetUnionBySize.Node node3 = dsu.makeSet(3); + DisjointSetUnionBySize.Node node4 = dsu.makeSet(4); + DisjointSetUnionBySize.Node node5 = dsu.makeSet(5); + DisjointSetUnionBySize.Node node6 = dsu.makeSet(6); + + // Create two separate components + dsu.unionSets(node1, node2); + dsu.unionSets(node2, node3); + + dsu.unionSets(node4, node5); + dsu.unionSets(node5, node6); + + // Verify they are separate + assertEquals(dsu.findSet(node1), dsu.findSet(node2)); + assertEquals(dsu.findSet(node2), dsu.findSet(node3)); + assertEquals(dsu.findSet(node4), dsu.findSet(node5)); + assertEquals(dsu.findSet(node5), dsu.findSet(node6)); + + assertNotEquals(dsu.findSet(node1), dsu.findSet(node4)); + assertNotEquals(dsu.findSet(node3), dsu.findSet(node6)); + } + + @Test + public void testEmptyValues() { + DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); + DisjointSetUnionBySize.Node emptyNode = dsu.makeSet(""); + DisjointSetUnionBySize.Node nullNode = dsu.makeSet(null); + + assertEquals(emptyNode, dsu.findSet(emptyNode)); + assertEquals(nullNode, dsu.findSet(nullNode)); + + dsu.unionSets(emptyNode, nullNode); + assertEquals(dsu.findSet(emptyNode), dsu.findSet(nullNode)); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/lists/CircularDoublyLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/CircularDoublyLinkedListTest.java index faa2765a3264..aba411bb524b 100644 --- a/src/test/java/com/thealgorithms/datastructures/lists/CircularDoublyLinkedListTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/CircularDoublyLinkedListTest.java @@ -1,120 +1,120 @@ -package com.thealgorithms.datastructures.lists; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class CircularDoublyLinkedListTest { - - private CircularDoublyLinkedList list; - - @BeforeEach - public void setUp() { - list = new CircularDoublyLinkedList<>(); - } - - @Test - public void testInitialSize() { - assertEquals(0, list.getSize(), "Initial size should be 0."); - } - - @Test - public void testAppendAndSize() { - list.append(10); - list.append(20); - list.append(30); - - assertEquals(3, list.getSize(), "Size after appends should be 3."); - assertEquals("[ 10, 20, 30 ]", list.toString(), "List content should match appended values."); - } - - @Test - public void testRemove() { - list.append(10); - list.append(20); - list.append(30); - - int removed = list.remove(1); - assertEquals(20, removed, "Removed element at index 1 should be 20."); - - assertEquals("[ 10, 30 ]", list.toString(), "List content should reflect removal."); - assertEquals(2, list.getSize(), "Size after removal should be 2."); - - removed = list.remove(0); - assertEquals(10, removed, "Removed element at index 0 should be 10."); - assertEquals("[ 30 ]", list.toString(), "List content should reflect second removal."); - assertEquals(1, list.getSize(), "Size after second removal should be 1."); - } - - @Test - public void testRemoveInvalidIndex() { - list.append(10); - list.append(20); - - assertThrows(IndexOutOfBoundsException.class, () -> list.remove(2), "Removing at invalid index 2 should throw exception."); - assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1), "Removing at negative index should throw exception."); - } - - @Test - public void testToStringEmpty() { - assertEquals("[]", list.toString(), "Empty list should display as []."); - } - - @Test - public void testSingleElement() { - list.append(10); - - assertEquals(1, list.getSize(), "Size after adding single element should be 1."); - assertEquals("[ 10 ]", list.toString(), "Single element list string should be formatted correctly."); - int removed = list.remove(0); - assertEquals(10, removed, "Removed element should be the one appended."); - assertEquals("[]", list.toString(), "List should be empty after removing last element."); - assertEquals(0, list.getSize(), "Size after removing last element should be 0."); - } - - @Test - public void testNullAppend() { - assertThrows(NullPointerException.class, () -> list.append(null), "Appending null should throw NullPointerException."); - } - - @Test - public void testRemoveLastPosition() { - list.append(10); - list.append(20); - list.append(30); - int removed = list.remove(list.getSize() - 1); - assertEquals(30, removed, "Last element removed should be 30."); - assertEquals(2, list.getSize(), "Size should decrease after removing last element."); - } - - @Test - public void testRemoveFromEmptyThrows() { - assertThrows(IndexOutOfBoundsException.class, () -> list.remove(0), "Remove from empty list should throw."); - } - - @Test - public void testRepeatedAppendAndRemove() { - for (int i = 0; i < 100; i++) { - list.append(i); - } - assertEquals(100, list.getSize()); - - for (int i = 99; i >= 0; i--) { - int removed = list.remove(i); - assertEquals(i, removed, "Removed element should match appended value."); - } - assertEquals(0, list.getSize(), "List should be empty after all removes."); - } - - @Test - public void testToStringAfterMultipleRemoves() { - list.append(1); - list.append(2); - list.append(3); - list.remove(2); - list.remove(0); - assertEquals("[ 2 ]", list.toString(), "ToString should correctly represent remaining elements."); - } -} +package com.thealgorithms.datastructures.lists; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class CircularDoublyLinkedListTest { + + private CircularDoublyLinkedList list; + + @BeforeEach + public void setUp() { + list = new CircularDoublyLinkedList<>(); + } + + @Test + public void testInitialSize() { + assertEquals(0, list.getSize(), "Initial size should be 0."); + } + + @Test + public void testAppendAndSize() { + list.append(10); + list.append(20); + list.append(30); + + assertEquals(3, list.getSize(), "Size after appends should be 3."); + assertEquals("[ 10, 20, 30 ]", list.toString(), "List content should match appended values."); + } + + @Test + public void testRemove() { + list.append(10); + list.append(20); + list.append(30); + + int removed = list.remove(1); + assertEquals(20, removed, "Removed element at index 1 should be 20."); + + assertEquals("[ 10, 30 ]", list.toString(), "List content should reflect removal."); + assertEquals(2, list.getSize(), "Size after removal should be 2."); + + removed = list.remove(0); + assertEquals(10, removed, "Removed element at index 0 should be 10."); + assertEquals("[ 30 ]", list.toString(), "List content should reflect second removal."); + assertEquals(1, list.getSize(), "Size after second removal should be 1."); + } + + @Test + public void testRemoveInvalidIndex() { + list.append(10); + list.append(20); + + assertThrows(IndexOutOfBoundsException.class, () -> list.remove(2), "Removing at invalid index 2 should throw exception."); + assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1), "Removing at negative index should throw exception."); + } + + @Test + public void testToStringEmpty() { + assertEquals("[]", list.toString(), "Empty list should display as []."); + } + + @Test + public void testSingleElement() { + list.append(10); + + assertEquals(1, list.getSize(), "Size after adding single element should be 1."); + assertEquals("[ 10 ]", list.toString(), "Single element list string should be formatted correctly."); + int removed = list.remove(0); + assertEquals(10, removed, "Removed element should be the one appended."); + assertEquals("[]", list.toString(), "List should be empty after removing last element."); + assertEquals(0, list.getSize(), "Size after removing last element should be 0."); + } + + @Test + public void testNullAppend() { + assertThrows(NullPointerException.class, () -> list.append(null), "Appending null should throw NullPointerException."); + } + + @Test + public void testRemoveLastPosition() { + list.append(10); + list.append(20); + list.append(30); + int removed = list.remove(list.getSize() - 1); + assertEquals(30, removed, "Last element removed should be 30."); + assertEquals(2, list.getSize(), "Size should decrease after removing last element."); + } + + @Test + public void testRemoveFromEmptyThrows() { + assertThrows(IndexOutOfBoundsException.class, () -> list.remove(0), "Remove from empty list should throw."); + } + + @Test + public void testRepeatedAppendAndRemove() { + for (int i = 0; i < 100; i++) { + list.append(i); + } + assertEquals(100, list.getSize()); + + for (int i = 99; i >= 0; i--) { + int removed = list.remove(i); + assertEquals(i, removed, "Removed element should match appended value."); + } + assertEquals(0, list.getSize(), "List should be empty after all removes."); + } + + @Test + public void testToStringAfterMultipleRemoves() { + list.append(1); + list.append(2); + list.append(3); + list.remove(2); + list.remove(0); + assertEquals("[ 2 ]", list.toString(), "ToString should correctly represent remaining elements."); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCountTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCountTest.java index 049804f58b5a..100029971d8d 100755 --- a/src/test/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCountTest.java +++ b/src/test/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCountTest.java @@ -1,15 +1,15 @@ -package com.thealgorithms.dynamicprogramming; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -public class UniqueSubsequencesCountTest { - - @ParameterizedTest - @CsvSource({"abc, 7", "abcdashgdhas, 3592", "a, 1", "'a b', 7", "a1b2, 15", "AaBb, 15", "abab, 11"}) - void subseqCountParameterizedTest(String input, int expected) { - assertEquals(expected, UniqueSubsequencesCount.countSubseq(input)); - } -} +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +public class UniqueSubsequencesCountTest { + + @ParameterizedTest + @CsvSource({"abc, 7", "abcdashgdhas, 3592", "a, 1", "'a b', 7", "a1b2, 15", "AaBb, 15", "abab, 11"}) + void subseqCountParameterizedTest(String input, int expected) { + assertEquals(expected, UniqueSubsequencesCount.countSubseq(input)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/VampireNumberTest.java b/src/test/java/com/thealgorithms/maths/VampireNumberTest.java index 6f331f1252cd..21976f9e57ad 100644 --- a/src/test/java/com/thealgorithms/maths/VampireNumberTest.java +++ b/src/test/java/com/thealgorithms/maths/VampireNumberTest.java @@ -1,32 +1,32 @@ -package com.thealgorithms.maths; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -class VampireNumberTest { - @Test - void areVampireNumbers() { - Assertions.assertTrue(VampireNumber.isVampireNumber(15, 93, true)); - Assertions.assertTrue(VampireNumber.isVampireNumber(135, 801, true)); - Assertions.assertTrue(VampireNumber.isVampireNumber(201, 600, true)); - } - - @Test - void arePseudoVampireNumbers() { - Assertions.assertTrue(VampireNumber.isVampireNumber(150, 93, false)); - Assertions.assertTrue(VampireNumber.isVampireNumber(546, 84, false)); - Assertions.assertTrue(VampireNumber.isVampireNumber(641, 65, false)); - } - - @Test - void areNotVampireNumbers() { - Assertions.assertFalse(VampireNumber.isVampireNumber(51, 39, false)); - Assertions.assertFalse(VampireNumber.isVampireNumber(51, 39, true)); - } - - @Test - void testSplitIntoSortedDigits() { - Assertions.assertEquals("123", VampireNumber.splitIntoSortedDigits(321)); - Assertions.assertEquals("02234", VampireNumber.splitIntoSortedDigits(20, 324)); - } -} +package com.thealgorithms.maths; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class VampireNumberTest { + @Test + void areVampireNumbers() { + Assertions.assertTrue(VampireNumber.isVampireNumber(15, 93, true)); + Assertions.assertTrue(VampireNumber.isVampireNumber(135, 801, true)); + Assertions.assertTrue(VampireNumber.isVampireNumber(201, 600, true)); + } + + @Test + void arePseudoVampireNumbers() { + Assertions.assertTrue(VampireNumber.isVampireNumber(150, 93, false)); + Assertions.assertTrue(VampireNumber.isVampireNumber(546, 84, false)); + Assertions.assertTrue(VampireNumber.isVampireNumber(641, 65, false)); + } + + @Test + void areNotVampireNumbers() { + Assertions.assertFalse(VampireNumber.isVampireNumber(51, 39, false)); + Assertions.assertFalse(VampireNumber.isVampireNumber(51, 39, true)); + } + + @Test + void testSplitIntoSortedDigits() { + Assertions.assertEquals("123", VampireNumber.splitIntoSortedDigits(321)); + Assertions.assertEquals("02234", VampireNumber.splitIntoSortedDigits(20, 324)); + } +} diff --git a/src/test/java/com/thealgorithms/others/IterativeFloodFillTest.java b/src/test/java/com/thealgorithms/others/IterativeFloodFillTest.java index 560f1df68e81..b86e154ad0d0 100644 --- a/src/test/java/com/thealgorithms/others/IterativeFloodFillTest.java +++ b/src/test/java/com/thealgorithms/others/IterativeFloodFillTest.java @@ -1,163 +1,163 @@ -package com.thealgorithms.others; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -import org.junit.jupiter.api.Test; - -class IterativeFloodFillTest { - - @Test - void testForEmptyImage() { - int[][] image = {}; - int[][] expected = {}; - - IterativeFloodFill.floodFill(image, 4, 5, 3, 2); - assertArrayEquals(expected, image); - } - - @Test - void testForSingleElementImage() { - int[][] image = {{1}}; - int[][] expected = {{3}}; - - IterativeFloodFill.floodFill(image, 0, 0, 3, 1); - assertArrayEquals(expected, image); - } - - @Test - void testForEmptyRow() { - int[][] image = {{}}; - int[][] expected = {{}}; - - IterativeFloodFill.floodFill(image, 4, 5, 3, 2); - assertArrayEquals(expected, image); - } - - @Test - void testForImageOne() { - int[][] image = { - {0, 0, 0, 0, 0, 0, 0}, - {0, 3, 3, 3, 3, 0, 0}, - {0, 3, 1, 1, 5, 0, 0}, - {0, 3, 1, 1, 5, 5, 3}, - {0, 3, 5, 5, 1, 1, 3}, - {0, 0, 0, 5, 1, 1, 3}, - {0, 0, 0, 3, 3, 3, 3}, - }; - - int[][] expected = { - {0, 0, 0, 0, 0, 0, 0}, - {0, 3, 3, 3, 3, 0, 0}, - {0, 3, 2, 2, 5, 0, 0}, - {0, 3, 2, 2, 5, 5, 3}, - {0, 3, 5, 5, 2, 2, 3}, - {0, 0, 0, 5, 2, 2, 3}, - {0, 0, 0, 3, 3, 3, 3}, - }; - - IterativeFloodFill.floodFill(image, 2, 2, 2, 1); - assertArrayEquals(expected, image); - } - - @Test - void testForImageTwo() { - int[][] image = { - {0, 0, 1, 1, 0, 0, 0}, - {1, 1, 3, 3, 3, 0, 0}, - {1, 3, 1, 1, 5, 0, 0}, - {0, 3, 1, 1, 5, 5, 3}, - {0, 3, 5, 5, 1, 1, 3}, - {0, 0, 0, 5, 1, 1, 3}, - {0, 0, 0, 1, 3, 1, 3}, - }; - - int[][] expected = { - {0, 0, 2, 2, 0, 0, 0}, - {2, 2, 3, 3, 3, 0, 0}, - {2, 3, 2, 2, 5, 0, 0}, - {0, 3, 2, 2, 5, 5, 3}, - {0, 3, 5, 5, 2, 2, 3}, - {0, 0, 0, 5, 2, 2, 3}, - {0, 0, 0, 2, 3, 2, 3}, - }; - - IterativeFloodFill.floodFill(image, 2, 2, 2, 1); - assertArrayEquals(expected, image); - } - - @Test - void testForImageThree() { - int[][] image = { - {1, 1, 2, 3, 1, 1, 1}, - {1, 0, 0, 1, 0, 0, 1}, - {1, 1, 1, 0, 3, 1, 2}, - }; - - int[][] expected = { - {4, 4, 2, 3, 4, 4, 4}, - {4, 0, 0, 4, 0, 0, 4}, - {4, 4, 4, 0, 3, 4, 2}, - }; - - IterativeFloodFill.floodFill(image, 0, 1, 4, 1); - assertArrayEquals(expected, image); - } - - @Test - void testForSameNewAndOldColor() { - int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; - - int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; - - IterativeFloodFill.floodFill(image, 0, 1, 1, 1); - assertArrayEquals(expected, image); - } - - @Test - void testForBigImage() { - int[][] image = new int[100][100]; - - assertDoesNotThrow(() -> IterativeFloodFill.floodFill(image, 0, 0, 1, 0)); - } - - @Test - void testForBelowZeroX() { - int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; - - int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; - - IterativeFloodFill.floodFill(image, -1, 1, 1, 0); - assertArrayEquals(expected, image); - } - - @Test - void testForBelowZeroY() { - int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; - - int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; - - IterativeFloodFill.floodFill(image, 1, -1, 1, 0); - assertArrayEquals(expected, image); - } - - @Test - void testForAboveBoundaryX() { - int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; - - int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; - - IterativeFloodFill.floodFill(image, 100, 1, 1, 0); - assertArrayEquals(expected, image); - } - - @Test - void testForAboveBoundaryY() { - int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; - - int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; - - IterativeFloodFill.floodFill(image, 1, 100, 1, 0); - assertArrayEquals(expected, image); - } -} +package com.thealgorithms.others; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +class IterativeFloodFillTest { + + @Test + void testForEmptyImage() { + int[][] image = {}; + int[][] expected = {}; + + IterativeFloodFill.floodFill(image, 4, 5, 3, 2); + assertArrayEquals(expected, image); + } + + @Test + void testForSingleElementImage() { + int[][] image = {{1}}; + int[][] expected = {{3}}; + + IterativeFloodFill.floodFill(image, 0, 0, 3, 1); + assertArrayEquals(expected, image); + } + + @Test + void testForEmptyRow() { + int[][] image = {{}}; + int[][] expected = {{}}; + + IterativeFloodFill.floodFill(image, 4, 5, 3, 2); + assertArrayEquals(expected, image); + } + + @Test + void testForImageOne() { + int[][] image = { + {0, 0, 0, 0, 0, 0, 0}, + {0, 3, 3, 3, 3, 0, 0}, + {0, 3, 1, 1, 5, 0, 0}, + {0, 3, 1, 1, 5, 5, 3}, + {0, 3, 5, 5, 1, 1, 3}, + {0, 0, 0, 5, 1, 1, 3}, + {0, 0, 0, 3, 3, 3, 3}, + }; + + int[][] expected = { + {0, 0, 0, 0, 0, 0, 0}, + {0, 3, 3, 3, 3, 0, 0}, + {0, 3, 2, 2, 5, 0, 0}, + {0, 3, 2, 2, 5, 5, 3}, + {0, 3, 5, 5, 2, 2, 3}, + {0, 0, 0, 5, 2, 2, 3}, + {0, 0, 0, 3, 3, 3, 3}, + }; + + IterativeFloodFill.floodFill(image, 2, 2, 2, 1); + assertArrayEquals(expected, image); + } + + @Test + void testForImageTwo() { + int[][] image = { + {0, 0, 1, 1, 0, 0, 0}, + {1, 1, 3, 3, 3, 0, 0}, + {1, 3, 1, 1, 5, 0, 0}, + {0, 3, 1, 1, 5, 5, 3}, + {0, 3, 5, 5, 1, 1, 3}, + {0, 0, 0, 5, 1, 1, 3}, + {0, 0, 0, 1, 3, 1, 3}, + }; + + int[][] expected = { + {0, 0, 2, 2, 0, 0, 0}, + {2, 2, 3, 3, 3, 0, 0}, + {2, 3, 2, 2, 5, 0, 0}, + {0, 3, 2, 2, 5, 5, 3}, + {0, 3, 5, 5, 2, 2, 3}, + {0, 0, 0, 5, 2, 2, 3}, + {0, 0, 0, 2, 3, 2, 3}, + }; + + IterativeFloodFill.floodFill(image, 2, 2, 2, 1); + assertArrayEquals(expected, image); + } + + @Test + void testForImageThree() { + int[][] image = { + {1, 1, 2, 3, 1, 1, 1}, + {1, 0, 0, 1, 0, 0, 1}, + {1, 1, 1, 0, 3, 1, 2}, + }; + + int[][] expected = { + {4, 4, 2, 3, 4, 4, 4}, + {4, 0, 0, 4, 0, 0, 4}, + {4, 4, 4, 0, 3, 4, 2}, + }; + + IterativeFloodFill.floodFill(image, 0, 1, 4, 1); + assertArrayEquals(expected, image); + } + + @Test + void testForSameNewAndOldColor() { + int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + IterativeFloodFill.floodFill(image, 0, 1, 1, 1); + assertArrayEquals(expected, image); + } + + @Test + void testForBigImage() { + int[][] image = new int[100][100]; + + assertDoesNotThrow(() -> IterativeFloodFill.floodFill(image, 0, 0, 1, 0)); + } + + @Test + void testForBelowZeroX() { + int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + IterativeFloodFill.floodFill(image, -1, 1, 1, 0); + assertArrayEquals(expected, image); + } + + @Test + void testForBelowZeroY() { + int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + IterativeFloodFill.floodFill(image, 1, -1, 1, 0); + assertArrayEquals(expected, image); + } + + @Test + void testForAboveBoundaryX() { + int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + IterativeFloodFill.floodFill(image, 100, 1, 1, 0); + assertArrayEquals(expected, image); + } + + @Test + void testForAboveBoundaryY() { + int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + IterativeFloodFill.floodFill(image, 1, 100, 1, 0); + assertArrayEquals(expected, image); + } +} diff --git a/src/test/java/com/thealgorithms/searches/TestSearchInARowAndColWiseSortedMatrix.java b/src/test/java/com/thealgorithms/searches/TestSearchInARowAndColWiseSortedMatrix.java index a56f79670cf3..8fc538be2cf0 100644 --- a/src/test/java/com/thealgorithms/searches/TestSearchInARowAndColWiseSortedMatrix.java +++ b/src/test/java/com/thealgorithms/searches/TestSearchInARowAndColWiseSortedMatrix.java @@ -1,25 +1,25 @@ -package com.thealgorithms.searches; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; - -import org.junit.jupiter.api.Test; - -public class TestSearchInARowAndColWiseSortedMatrix { - @Test - public void searchItem() { - int[][] matrix = {{3, 4, 5, 6, 7}, {8, 9, 10, 11, 12}, {14, 15, 16, 17, 18}, {23, 24, 25, 26, 27}, {30, 31, 32, 33, 34}}; - var test = new SearchInARowAndColWiseSortedMatrix(); - int[] res = test.search(matrix, 16); - int[] expectedResult = {2, 2}; - assertArrayEquals(expectedResult, res); - } - - @Test - public void notFound() { - int[][] matrix = {{3, 4, 5, 6, 7}, {8, 9, 10, 11, 12}, {14, 15, 16, 17, 18}, {23, 24, 25, 26, 27}, {30, 31, 32, 33, 34}}; - var test = new SearchInARowAndColWiseSortedMatrix(); - int[] res = test.search(matrix, 96); - int[] expectedResult = {-1, -1}; - assertArrayEquals(expectedResult, res); - } -} +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.Test; + +public class TestSearchInARowAndColWiseSortedMatrix { + @Test + public void searchItem() { + int[][] matrix = {{3, 4, 5, 6, 7}, {8, 9, 10, 11, 12}, {14, 15, 16, 17, 18}, {23, 24, 25, 26, 27}, {30, 31, 32, 33, 34}}; + var test = new SearchInARowAndColWiseSortedMatrix(); + int[] res = test.search(matrix, 16); + int[] expectedResult = {2, 2}; + assertArrayEquals(expectedResult, res); + } + + @Test + public void notFound() { + int[][] matrix = {{3, 4, 5, 6, 7}, {8, 9, 10, 11, 12}, {14, 15, 16, 17, 18}, {23, 24, 25, 26, 27}, {30, 31, 32, 33, 34}}; + var test = new SearchInARowAndColWiseSortedMatrix(); + int[] res = test.search(matrix, 96); + int[] expectedResult = {-1, -1}; + assertArrayEquals(expectedResult, res); + } +}