diff --git a/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java b/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java
new file mode 100644
index 000000000000..0dd23e937953
--- /dev/null
+++ b/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java
@@ -0,0 +1,48 @@
+package com.thealgorithms.audiofilters;
+
+/**
+ * Exponential Moving Average (EMA) Filter for smoothing audio signals.
+ *
+ * This filter applies an exponential moving average to a sequence of audio
+ * signal values, making it useful for smoothing out rapid fluctuations.
+ * The smoothing factor (alpha) controls the degree of smoothing.
+ *
+ *
Based on the definition from
+ * Wikipedia link.
+ */
+public class EMAFilter {
+ private final double alpha;
+ private double emaValue;
+ /**
+ * Constructs an EMA filter with a given smoothing factor.
+ *
+ * @param alpha Smoothing factor (0 < alpha <= 1)
+ * @throws IllegalArgumentException if alpha is not in (0, 1]
+ */
+ public EMAFilter(double alpha) {
+ if (alpha <= 0 || alpha > 1) {
+ throw new IllegalArgumentException("Alpha must be between 0 and 1.");
+ }
+ this.alpha = alpha;
+ this.emaValue = 0.0;
+ }
+ /**
+ * Applies the EMA filter to an audio signal array.
+ *
+ * @param audioSignal Array of audio samples to process
+ * @return Array of processed (smoothed) samples
+ */
+ public double[] apply(double[] audioSignal) {
+ if (audioSignal.length == 0) {
+ return new double[0];
+ }
+ double[] emaSignal = new double[audioSignal.length];
+ emaValue = audioSignal[0];
+ emaSignal[0] = emaValue;
+ for (int i = 1; i < audioSignal.length; i++) {
+ emaValue = alpha * audioSignal[i] + (1 - alpha) * emaValue;
+ emaSignal[i] = emaValue;
+ }
+ return emaSignal;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/audiofilters/IIRFilter.java b/src/main/java/com/thealgorithms/audiofilters/IIRFilter.java
index c211cd08a501..fbc095909541 100644
--- a/src/main/java/com/thealgorithms/audiofilters/IIRFilter.java
+++ b/src/main/java/com/thealgorithms/audiofilters/IIRFilter.java
@@ -58,7 +58,7 @@ public void setCoeffs(double[] aCoeffs, double[] bCoeffs) throws IllegalArgument
throw new IllegalArgumentException("bCoeffs must be of size " + order + ", got " + bCoeffs.length);
}
- for (int i = 0; i <= order; i++) {
+ for (int i = 0; i < order; i++) {
coeffsA[i] = aCoeffs[i];
coeffsB[i] = bCoeffs[i];
}
diff --git a/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java b/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java
index a0b886e6be8a..f8cd0c40c20e 100644
--- a/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java
+++ b/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java
@@ -1,32 +1,54 @@
package com.thealgorithms.backtracking;
+import java.util.ArrayList;
import java.util.List;
-import java.util.TreeSet;
/**
- * Finds all permutations of 1...n of length k
- * @author TheClerici (git-TheClerici)
+ * This class provides methods to find all combinations of integers from 0 to n-1
+ * of a specified length k using backtracking.
*/
public final class ArrayCombination {
private ArrayCombination() {
}
- private static int length;
/**
- * Find all combinations of 1..n by creating an array and using backtracking in Combination.java
- * @param n max value of the array.
- * @param k length of combination
- * @return a list of all combinations of length k. If k == 0, return null.
+ * Generates all possible combinations of length k from the integers 0 to n-1.
+ *
+ * @param n The total number of elements (0 to n-1).
+ * @param k The desired length of each combination.
+ * @return A list containing all combinations of length k.
+ * @throws IllegalArgumentException if n or k are negative, or if k is greater than n.
*/
- public static List> combination(int n, int k) {
- if (n <= 0) {
- return null;
+ public static List> combination(int n, int k) {
+ if (n < 0 || k < 0 || k > n) {
+ throw new IllegalArgumentException("Invalid input: n must be non-negative, k must be non-negative and less than or equal to n.");
}
- length = k;
- Integer[] arr = new Integer[n];
- for (int i = 1; i <= n; i++) {
- arr[i - 1] = i;
+
+ List> combinations = new ArrayList<>();
+ combine(combinations, new ArrayList<>(), 0, n, k);
+ return combinations;
+ }
+
+ /**
+ * A helper method that uses backtracking to find combinations.
+ *
+ * @param combinations The list to store all valid combinations found.
+ * @param current The current combination being built.
+ * @param start The starting index for the current recursion.
+ * @param n The total number of elements (0 to n-1).
+ * @param k The desired length of each combination.
+ */
+ private static void combine(List> combinations, List current, int start, int n, int k) {
+ // Base case: combination found
+ if (current.size() == k) {
+ combinations.add(new ArrayList<>(current));
+ return;
+ }
+
+ for (int i = start; i < n; i++) {
+ current.add(i);
+ combine(combinations, current, i + 1, n, k);
+ current.remove(current.size() - 1); // Backtrack
}
- return Combination.combination(arr, length);
}
}
diff --git a/src/main/java/com/thealgorithms/backtracking/Combination.java b/src/main/java/com/thealgorithms/backtracking/Combination.java
index bf2a672a0ef8..ecaf7428f986 100644
--- a/src/main/java/com/thealgorithms/backtracking/Combination.java
+++ b/src/main/java/com/thealgorithms/backtracking/Combination.java
@@ -1,6 +1,7 @@
package com.thealgorithms.backtracking;
import java.util.Arrays;
+import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeSet;
@@ -13,8 +14,6 @@ public final class Combination {
private Combination() {
}
- private static int length;
-
/**
* Find all combinations of given array using backtracking
* @param arr the array.
@@ -23,39 +22,45 @@ private Combination() {
* @return a list of all combinations of length n. If n == 0, return null.
*/
public static List> combination(T[] arr, int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("The combination length cannot be negative.");
+ }
+
if (n == 0) {
- return null;
+ return Collections.emptyList();
}
- length = n;
T[] array = arr.clone();
Arrays.sort(array);
+
List> result = new LinkedList<>();
- backtracking(array, 0, new TreeSet(), result);
+ backtracking(array, n, 0, new TreeSet(), result);
return result;
}
/**
* Backtrack all possible combinations of a given array
* @param arr the array.
+ * @param n length of the combination
* @param index the starting index.
* @param currSet set that tracks current combination
* @param result the list contains all combination.
* @param the type of elements in the array.
*/
- private static void backtracking(T[] arr, int index, TreeSet currSet, List> result) {
- if (index + length - currSet.size() > arr.length) {
+ private static void backtracking(T[] arr, int n, int index, TreeSet currSet, List> result) {
+ if (index + n - currSet.size() > arr.length) {
return;
}
- if (length - 1 == currSet.size()) {
+ if (currSet.size() == n - 1) {
for (int i = index; i < arr.length; i++) {
currSet.add(arr[i]);
- result.add((TreeSet) currSet.clone());
+ result.add(new TreeSet<>(currSet));
currSet.remove(arr[i]);
}
+ return;
}
for (int i = index; i < arr.length; i++) {
currSet.add(arr[i]);
- backtracking(arr, i + 1, currSet, result);
+ backtracking(arr, n, i + 1, currSet, result);
currSet.remove(arr[i]);
}
}
diff --git a/src/main/java/com/thealgorithms/backtracking/CrosswordSolver.java b/src/main/java/com/thealgorithms/backtracking/CrosswordSolver.java
new file mode 100644
index 000000000000..6bfb026c7de9
--- /dev/null
+++ b/src/main/java/com/thealgorithms/backtracking/CrosswordSolver.java
@@ -0,0 +1,125 @@
+package com.thealgorithms.backtracking;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A class to solve a crossword puzzle using backtracking.
+ * Example:
+ * Input:
+ * puzzle = {
+ * {' ', ' ', ' '},
+ * {' ', ' ', ' '},
+ * {' ', ' ', ' '}
+ * }
+ * words = List.of("cat", "dog")
+ *
+ * Output:
+ * {
+ * {'c', 'a', 't'},
+ * {' ', ' ', ' '},
+ * {'d', 'o', 'g'}
+ * }
+ */
+public final class CrosswordSolver {
+ private CrosswordSolver() {
+ }
+
+ /**
+ * Checks if a word can be placed at the specified position in the crossword.
+ *
+ * @param puzzle The crossword puzzle represented as a 2D char array.
+ * @param word The word to be placed.
+ * @param row The row index where the word might be placed.
+ * @param col The column index where the word might be placed.
+ * @param vertical If true, the word is placed vertically; otherwise, horizontally.
+ * @return true if the word can be placed, false otherwise.
+ */
+ public static boolean isValid(char[][] puzzle, String word, int row, int col, boolean vertical) {
+ for (int i = 0; i < word.length(); i++) {
+ if (vertical) {
+ if (row + i >= puzzle.length || puzzle[row + i][col] != ' ') {
+ return false;
+ }
+ } else {
+ if (col + i >= puzzle[0].length || puzzle[row][col + i] != ' ') {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Places a word at the specified position in the crossword.
+ *
+ * @param puzzle The crossword puzzle represented as a 2D char array.
+ * @param word The word to be placed.
+ * @param row The row index where the word will be placed.
+ * @param col The column index where the word will be placed.
+ * @param vertical If true, the word is placed vertically; otherwise, horizontally.
+ */
+ public static void placeWord(char[][] puzzle, String word, int row, int col, boolean vertical) {
+ for (int i = 0; i < word.length(); i++) {
+ if (vertical) {
+ puzzle[row + i][col] = word.charAt(i);
+ } else {
+ puzzle[row][col + i] = word.charAt(i);
+ }
+ }
+ }
+
+ /**
+ * Removes a word from the specified position in the crossword.
+ *
+ * @param puzzle The crossword puzzle represented as a 2D char array.
+ * @param word The word to be removed.
+ * @param row The row index where the word is placed.
+ * @param col The column index where the word is placed.
+ * @param vertical If true, the word was placed vertically; otherwise, horizontally.
+ */
+ public static void removeWord(char[][] puzzle, String word, int row, int col, boolean vertical) {
+ for (int i = 0; i < word.length(); i++) {
+ if (vertical) {
+ puzzle[row + i][col] = ' ';
+ } else {
+ puzzle[row][col + i] = ' ';
+ }
+ }
+ }
+
+ /**
+ * Solves the crossword puzzle using backtracking.
+ *
+ * @param puzzle The crossword puzzle represented as a 2D char array.
+ * @param words The list of words to be placed.
+ * @return true if the crossword is solved, false otherwise.
+ */
+ public static boolean solveCrossword(char[][] puzzle, Collection words) {
+ // Create a mutable copy of the words list
+ List remainingWords = new ArrayList<>(words);
+
+ for (int row = 0; row < puzzle.length; row++) {
+ for (int col = 0; col < puzzle[0].length; col++) {
+ if (puzzle[row][col] == ' ') {
+ for (String word : new ArrayList<>(remainingWords)) {
+ for (boolean vertical : new boolean[] {true, false}) {
+ if (isValid(puzzle, word, row, col, vertical)) {
+ placeWord(puzzle, word, row, col, vertical);
+ remainingWords.remove(word);
+ if (solveCrossword(puzzle, remainingWords)) {
+ return true;
+ }
+ remainingWords.add(word);
+ removeWord(puzzle, word, row, col, vertical);
+ }
+ }
+ }
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/backtracking/KnightsTour.java b/src/main/java/com/thealgorithms/backtracking/KnightsTour.java
index 2287b39da385..2c2da659f3aa 100644
--- a/src/main/java/com/thealgorithms/backtracking/KnightsTour.java
+++ b/src/main/java/com/thealgorithms/backtracking/KnightsTour.java
@@ -4,33 +4,26 @@
import java.util.Comparator;
import java.util.List;
-/*
- * Problem Statement: -
-
- Given a N*N board with the Knight placed on the first block of an empty board. Moving according
- to the rules of chess knight must visit each square exactly once. Print the order of each cell in
- which they are visited.
-
- Example: -
-
- Input : N = 8
-
- Output:
- 0 59 38 33 30 17 8 63
- 37 34 31 60 9 62 29 16
- 58 1 36 39 32 27 18 7
- 35 48 41 26 61 10 15 28
- 42 57 2 49 40 23 6 19
- 47 50 45 54 25 20 11 14
- 56 43 52 3 22 13 24 5
- 51 46 55 44 53 4 21 12
-
+/**
+ * The KnightsTour class solves the Knight's Tour problem using backtracking.
+ *
+ * Problem Statement:
+ * Given an N*N board with a knight placed on the first block, the knight must
+ * move according to chess rules and visit each square on the board exactly once.
+ * The class outputs the sequence of moves for the knight.
+ *
+ * Example:
+ * Input: N = 8 (8x8 chess board)
+ * Output: The sequence of numbers representing the order in which the knight visits each square.
*/
public final class KnightsTour {
private KnightsTour() {
}
+ // The size of the chess board (12x12 grid, with 2 extra rows/columns as a buffer around a 8x8 area)
private static final int BASE = 12;
+
+ // Possible moves for a knight in chess
private static final int[][] MOVES = {
{1, -2},
{2, -1},
@@ -40,36 +33,40 @@ private KnightsTour() {
{-2, 1},
{-2, -1},
{-1, -2},
- }; // Possible moves by knight on chess
- private static int[][] grid; // chess grid
- private static int total; // total squares in chess
+ };
+
+ // Chess grid representing the board
+ static int[][] grid;
+
+ // Total number of cells the knight needs to visit
+ static int total;
- public static void main(String[] args) {
+ /**
+ * Resets the chess board to its initial state.
+ * Initializes the grid with boundary cells marked as -1 and internal cells as 0.
+ * Sets the total number of cells the knight needs to visit.
+ */
+ public static void resetBoard() {
grid = new int[BASE][BASE];
total = (BASE - 4) * (BASE - 4);
-
for (int r = 0; r < BASE; r++) {
for (int c = 0; c < BASE; c++) {
if (r < 2 || r > BASE - 3 || c < 2 || c > BASE - 3) {
- grid[r][c] = -1;
+ grid[r][c] = -1; // Mark boundary cells
}
}
}
-
- int row = 2 + (int) (Math.random() * (BASE - 4));
- int col = 2 + (int) (Math.random() * (BASE - 4));
-
- grid[row][col] = 1;
-
- if (solve(row, col, 2)) {
- printResult();
- } else {
- System.out.println("no result");
- }
}
- // Return True when solvable
- private static boolean solve(int row, int column, int count) {
+ /**
+ * Recursive method to solve the Knight's Tour problem.
+ *
+ * @param row The current row of the knight
+ * @param column The current column of the knight
+ * @param count The current move number
+ * @return True if a solution is found, False otherwise
+ */
+ static boolean solve(int row, int column, int count) {
if (count > total) {
return true;
}
@@ -80,29 +77,37 @@ private static boolean solve(int row, int column, int count) {
return false;
}
+ // Sort neighbors by Warnsdorff's rule (fewest onward moves)
neighbor.sort(Comparator.comparingInt(a -> a[2]));
for (int[] nb : neighbor) {
- row = nb[0];
- column = nb[1];
- grid[row][column] = count;
- if (!orphanDetected(count, row, column) && solve(row, column, count + 1)) {
+ int nextRow = nb[0];
+ int nextCol = nb[1];
+ grid[nextRow][nextCol] = count;
+ if (!orphanDetected(count, nextRow, nextCol) && solve(nextRow, nextCol, count + 1)) {
return true;
}
- grid[row][column] = 0;
+ grid[nextRow][nextCol] = 0; // Backtrack
}
return false;
}
- // Returns List of neighbours
- private static List neighbors(int row, int column) {
+ /**
+ * Returns a list of valid neighboring cells where the knight can move.
+ *
+ * @param row The current row of the knight
+ * @param column The current column of the knight
+ * @return A list of arrays representing valid moves, where each array contains:
+ * {nextRow, nextCol, numberOfPossibleNextMoves}
+ */
+ static List neighbors(int row, int column) {
List neighbour = new ArrayList<>();
for (int[] m : MOVES) {
int x = m[0];
int y = m[1];
- if (grid[row + y][column + x] == 0) {
+ if (row + y >= 0 && row + y < BASE && column + x >= 0 && column + x < BASE && grid[row + y][column + x] == 0) {
int num = countNeighbors(row + y, column + x);
neighbour.add(new int[] {row + y, column + x, num});
}
@@ -110,19 +115,34 @@ private static List neighbors(int row, int column) {
return neighbour;
}
- // Returns the total count of neighbors
- private static int countNeighbors(int row, int column) {
+ /**
+ * Counts the number of possible valid moves for a knight from a given position.
+ *
+ * @param row The row of the current position
+ * @param column The column of the current position
+ * @return The number of valid neighboring moves
+ */
+ static int countNeighbors(int row, int column) {
int num = 0;
for (int[] m : MOVES) {
- if (grid[row + m[1]][column + m[0]] == 0) {
+ int x = m[0];
+ int y = m[1];
+ if (row + y >= 0 && row + y < BASE && column + x >= 0 && column + x < BASE && grid[row + y][column + x] == 0) {
num++;
}
}
return num;
}
- // Returns true if it is orphan
- private static boolean orphanDetected(int count, int row, int column) {
+ /**
+ * Detects if moving to a given position will create an orphan (a position with no further valid moves).
+ *
+ * @param count The current move number
+ * @param row The row of the current position
+ * @param column The column of the current position
+ * @return True if an orphan is detected, False otherwise
+ */
+ static boolean orphanDetected(int count, int row, int column) {
if (count < total - 1) {
List neighbor = neighbors(row, column);
for (int[] nb : neighbor) {
@@ -133,17 +153,4 @@ private static boolean orphanDetected(int count, int row, int column) {
}
return false;
}
-
- // Prints the result grid
- private static void printResult() {
- for (int[] row : grid) {
- for (int i : row) {
- if (i == -1) {
- continue;
- }
- System.out.printf("%2d ", i);
- }
- System.out.println();
- }
- }
}
diff --git a/src/main/java/com/thealgorithms/backtracking/MColoring.java b/src/main/java/com/thealgorithms/backtracking/MColoring.java
index f069e46cc627..d0188dfd13aa 100644
--- a/src/main/java/com/thealgorithms/backtracking/MColoring.java
+++ b/src/main/java/com/thealgorithms/backtracking/MColoring.java
@@ -7,64 +7,82 @@
import java.util.Set;
/**
+ * Node class represents a graph node. Each node is associated with a color
+ * (initially 1) and contains a set of edges representing its adjacent nodes.
+ *
* @author Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi)
*/
class Node {
- int color = 1;
- Set edges = new HashSet();
+ int color = 1; // Initial color for each node
+ Set edges = new HashSet(); // Set of edges representing adjacent nodes
}
+/**
+ * MColoring class solves the M-Coloring problem where the goal is to determine
+ * if it's possible to color a graph using at most M colors such that no two
+ * adjacent nodes have the same color.
+ */
public final class MColoring {
+
private MColoring() {
- }
- static int possiblePaint(ArrayList nodes, int n, int m) {
+ } // Prevent instantiation of utility class
- // Create a visited array of n nodes
+ /**
+ * Determines whether it is possible to color the graph using at most M colors.
+ *
+ * @param nodes List of nodes representing the graph.
+ * @param n The total number of nodes in the graph.
+ * @param m The maximum number of allowed colors.
+ * @return true if the graph can be colored using M colors, false otherwise.
+ */
+ static boolean isColoringPossible(ArrayList nodes, int n, int m) {
+
+ // Visited array keeps track of whether each node has been processed.
ArrayList visited = new ArrayList();
for (int i = 0; i < n + 1; i++) {
- visited.add(0);
+ visited.add(0); // Initialize all nodes as unvisited (0)
}
- // maxColors used till now are 1 as
- // all nodes are painted color 1
+ // The number of colors used so far (initially set to 1, since all nodes
+ // start with color 1).
int maxColors = 1;
+ // Loop through all the nodes to ensure every node is visited, in case the
+ // graph is disconnected.
for (int sv = 1; sv <= n; sv++) {
if (visited.get(sv) > 0) {
- continue;
+ continue; // Skip nodes that are already visited
}
- // If the starting point is unvisited,
- // mark it visited and push it in queue
+ // If the node is unvisited, mark it as visited and add it to the queue for BFS.
visited.set(sv, 1);
Queue q = new LinkedList<>();
q.add(sv);
- // BFS
+ // Perform BFS to process all nodes and their adjacent nodes
while (q.size() != 0) {
- int top = q.peek();
+ int top = q.peek(); // Get the current node from the queue
q.remove();
- // Checking all adjacent nodes
- // to "top" edge in our queue
+ // Check all adjacent nodes of the current node
for (int it : nodes.get(top).edges) {
- // If the color of the
- // adjacent node is same, increase it by
- // 1
+ // If the adjacent node has the same color as the current node, increment its
+ // color to avoid conflict.
if (nodes.get(top).color == nodes.get(it).color) {
nodes.get(it).color += 1;
}
- // If number of colors used exceeds m,
- // return 0
+ // Keep track of the maximum number of colors used so far
maxColors = Math.max(maxColors, Math.max(nodes.get(top).color, nodes.get(it).color));
+
+ // If the number of colors used exceeds the allowed limit M, return false.
if (maxColors > m) {
- return 0;
+ return false;
}
- // If the adjacent node is not visited,
- // mark it visited and push it in queue
+ // If the adjacent node hasn't been visited yet, mark it as visited and add it
+ // to the queue for further processing.
if (visited.get(it) == 0) {
visited.set(it, 1);
q.add(it);
@@ -72,6 +90,7 @@ static int possiblePaint(ArrayList nodes, int n, int m) {
}
}
}
- return 1;
+
+ return true; // Possible to color the entire graph with M or fewer colors.
}
}
diff --git a/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java b/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java
index f7eae01e449a..8247172e7ee0 100644
--- a/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java
+++ b/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java
@@ -1,152 +1,125 @@
package com.thealgorithms.backtracking;
+/**
+ * This class contains methods to solve a maze using recursive backtracking.
+ * The maze is represented as a 2D array where walls, paths, and visited/dead
+ * ends are marked with different integers.
+ *
+ * The goal is to find a path from a starting position to the target position
+ * (map[6][5]) while navigating through the maze.
+ */
public final class MazeRecursion {
+
private MazeRecursion() {
}
- public static void mazeRecursion() {
- // First create a 2 dimensions array to mimic a maze map
- int[][] map = new int[8][7];
- int[][] map2 = new int[8][7];
-
- // We use 1 to indicate wall
- // Set the ceiling and floor to 1
- for (int i = 0; i < 7; i++) {
- map[0][i] = 1;
- map[7][i] = 1;
- }
-
- // Then we set the left and right wall to 1
- for (int i = 0; i < 8; i++) {
- map[i][0] = 1;
- map[i][6] = 1;
- }
-
- // Now we have created a maze with its wall initialized
-
- // Here we set the obstacle
- map[3][1] = 1;
- map[3][2] = 1;
-
- // Print the current map
- System.out.println("The condition of the map: ");
- for (int i = 0; i < 8; i++) {
- for (int j = 0; j < 7; j++) {
- System.out.print(map[i][j] + " ");
- }
- System.out.println();
- }
-
- // clone another map for setWay2 method
- for (int i = 0; i < map.length; i++) {
- System.arraycopy(map[i], 0, map2[i], 0, map[i].length);
- }
-
- // By using recursive backtracking to let your ball(target) find its way in the
- // maze
- // The first parameter is the map
- // Second parameter is x coordinate of your target
- // Third parameter is the y coordinate of your target
- setWay(map, 1, 1);
- setWay2(map2, 1, 1);
-
- // Print out the new map1, with the ball footprint
- System.out.println("After the ball goes through the map1,show the current map1 condition");
- for (int i = 0; i < 8; i++) {
- for (int j = 0; j < 7; j++) {
- System.out.print(map[i][j] + " ");
- }
- System.out.println();
+ /**
+ * This method solves the maze using the "down -> right -> up -> left"
+ * movement strategy.
+ *
+ * @param map The 2D array representing the maze (walls, paths, etc.)
+ * @return The solved maze with paths marked, or null if no solution exists.
+ */
+ public static int[][] solveMazeUsingFirstStrategy(int[][] map) {
+ if (setWay(map, 1, 1)) {
+ return map;
}
+ return null;
+ }
- // Print out the new map2, with the ball footprint
- System.out.println("After the ball goes through the map2,show the current map2 condition");
- for (int i = 0; i < 8; i++) {
- for (int j = 0; j < 7; j++) {
- System.out.print(map2[i][j] + " ");
- }
- System.out.println();
+ /**
+ * This method solves the maze using the "up -> right -> down -> left"
+ * movement strategy.
+ *
+ * @param map The 2D array representing the maze (walls, paths, etc.)
+ * @return The solved maze with paths marked, or null if no solution exists.
+ */
+ public static int[][] solveMazeUsingSecondStrategy(int[][] map) {
+ if (setWay2(map, 1, 1)) {
+ return map;
}
+ return null;
}
/**
- * Using recursive path finding to help the ball find its way in the maze
- * Description:
- * 1. map (means the maze)
- * 2. i, j (means the initial coordinate of the ball in the maze)
- * 3. if the ball can reach the end of maze, that is position of map[6][5],
- * means the we have found a path for the ball
- * 4. Additional Information: 0 in the map[i][j] means the ball has not gone
- * through this position, 1 means the wall, 2 means the path is feasible, 3
- * means the ball has gone through the path but this path is dead end
- * 5. We will need strategy for the ball to pass through the maze for example:
- * Down -> Right -> Up -> Left, if the path doesn't work, then backtrack
+ * Attempts to find a path through the maze using a "down -> right -> up -> left"
+ * movement strategy. The path is marked with '2' for valid paths and '3' for dead ends.
*
- * @author OngLipWei
- * @version Jun 23, 2021 11:36:14 AM
- * @param map The maze
- * @param i x coordinate of your ball(target)
- * @param j y coordinate of your ball(target)
- * @return If we did find a path for the ball,return true,else false
+ * @param map The 2D array representing the maze (walls, paths, etc.)
+ * @param i The current x-coordinate of the ball (row index)
+ * @param j The current y-coordinate of the ball (column index)
+ * @return True if a path is found to (6,5), otherwise false
*/
- public static boolean setWay(int[][] map, int i, int j) {
- if (map[6][5] == 2) { // means the ball find its path, ending condition
+ private static boolean setWay(int[][] map, int i, int j) {
+ if (map[6][5] == 2) {
return true;
}
- if (map[i][j] == 0) { // if the ball haven't gone through this point
- // then the ball follows the move strategy : down -> right -> up -> left
- map[i][j] = 2; // we assume that this path is feasible first, set the current point to 2
- // first。
- if (setWay(map, i + 1, j)) { // go down
+
+ // If the current position is unvisited (0), explore it
+ if (map[i][j] == 0) {
+ // Mark the current position as '2'
+ map[i][j] = 2;
+
+ // Move down
+ if (setWay(map, i + 1, j)) {
return true;
- } else if (setWay(map, i, j + 1)) { // go right
+ }
+ // Move right
+ else if (setWay(map, i, j + 1)) {
return true;
- } else if (setWay(map, i - 1, j)) { // go up
+ }
+ // Move up
+ else if (setWay(map, i - 1, j)) {
return true;
- } else if (setWay(map, i, j - 1)) { // go left
+ }
+ // Move left
+ else if (setWay(map, i, j - 1)) {
return true;
- } else {
- // means that the current point is the dead end, the ball cannot proceed, set
- // the current point to 3 and return false, the backtracking will start, it will
- // go to the previous step and check for feasible path again
- map[i][j] = 3;
- return false;
}
- } else { // if the map[i][j] != 0 , it will probably be 1,2,3, return false because the
- // ball cannot hit the wall, cannot go to the path that has gone though before,
- // and cannot head to deadened.
+
+ map[i][j] = 3; // Mark as dead end (3) if no direction worked
return false;
}
+ return false;
}
- // Here is another move strategy for the ball: up->right->down->left
- public static boolean setWay2(int[][] map, int i, int j) {
- if (map[6][5] == 2) { // means the ball find its path, ending condition
+ /**
+ * Attempts to find a path through the maze using an alternative movement
+ * strategy "up -> right -> down -> left".
+ *
+ * @param map The 2D array representing the maze (walls, paths, etc.)
+ * @param i The current x-coordinate of the ball (row index)
+ * @param j The current y-coordinate of the ball (column index)
+ * @return True if a path is found to (6,5), otherwise false
+ */
+ private static boolean setWay2(int[][] map, int i, int j) {
+ if (map[6][5] == 2) {
return true;
}
- if (map[i][j] == 0) { // if the ball haven't gone through this point
- // then the ball follows the move strategy : up->right->down->left
- map[i][j] = 2; // we assume that this path is feasible first, set the current point to 2
- // first。
- if (setWay2(map, i - 1, j)) { // go up
+
+ if (map[i][j] == 0) {
+ map[i][j] = 2;
+
+ // Move up
+ if (setWay2(map, i - 1, j)) {
return true;
- } else if (setWay2(map, i, j + 1)) { // go right
+ }
+ // Move right
+ else if (setWay2(map, i, j + 1)) {
return true;
- } else if (setWay2(map, i + 1, j)) { // go down
+ }
+ // Move down
+ else if (setWay2(map, i + 1, j)) {
return true;
- } else if (setWay2(map, i, j - 1)) { // go left
+ }
+ // Move left
+ else if (setWay2(map, i, j - 1)) {
return true;
- } else {
- // means that the current point is the dead end, the ball cannot proceed, set
- // the current point to 3 and return false, the backtracking will start, it will
- // go to the previous step and check for feasible path again
- map[i][j] = 3;
- return false;
}
- } else { // if the map[i][j] != 0 , it will probably be 1,2,3, return false because the
- // ball cannot hit the wall, cannot go to the path that has gone through before,
- // and cannot head to deadend.
+
+ map[i][j] = 3; // Mark as dead end (3) if no direction worked
return false;
}
+ return false;
}
}
diff --git a/src/main/java/com/thealgorithms/backtracking/NQueens.java b/src/main/java/com/thealgorithms/backtracking/NQueens.java
index e21a8bb7174c..1a8e453e34cb 100644
--- a/src/main/java/com/thealgorithms/backtracking/NQueens.java
+++ b/src/main/java/com/thealgorithms/backtracking/NQueens.java
@@ -36,13 +36,10 @@ public final class NQueens {
private NQueens() {
}
- public static void main(String[] args) {
- placeQueens(1);
- placeQueens(2);
- placeQueens(3);
- placeQueens(4);
- placeQueens(5);
- placeQueens(6);
+ public static List> getNQueensArrangements(int queens) {
+ List> arrangements = new ArrayList<>();
+ getSolution(queens, arrangements, new int[queens], 0);
+ return arrangements;
}
public static void placeQueens(final int queens) {
diff --git a/src/main/java/com/thealgorithms/backtracking/ParenthesesGenerator.java b/src/main/java/com/thealgorithms/backtracking/ParenthesesGenerator.java
index 8bbed4106251..bf93f946ab7b 100644
--- a/src/main/java/com/thealgorithms/backtracking/ParenthesesGenerator.java
+++ b/src/main/java/com/thealgorithms/backtracking/ParenthesesGenerator.java
@@ -19,7 +19,7 @@ private ParenthesesGenerator() {
*/
public static List generateParentheses(final int n) {
if (n < 0) {
- throw new IllegalArgumentException("The number of pairs of parentheses cannot be nagative");
+ throw new IllegalArgumentException("The number of pairs of parentheses cannot be negative");
}
List result = new ArrayList<>();
generateParenthesesHelper(result, "", 0, 0, n);
diff --git a/src/main/java/com/thealgorithms/backtracking/PowerSum.java b/src/main/java/com/thealgorithms/backtracking/PowerSum.java
index 6617ea326a1c..b34ba660ebd7 100644
--- a/src/main/java/com/thealgorithms/backtracking/PowerSum.java
+++ b/src/main/java/com/thealgorithms/backtracking/PowerSum.java
@@ -1,45 +1,51 @@
package com.thealgorithms.backtracking;
-/*
- * Problem Statement :
- * Find the number of ways that a given integer, N , can be expressed as the sum of the Xth powers
- * of unique, natural numbers. For example, if N=100 and X=3, we have to find all combinations of
- * unique cubes adding up to 100. The only solution is 1^3+2^3+3^3+4^3. Therefore output will be 1.
+/**
+ * Problem Statement:
+ * Find the number of ways that a given integer, N, can be expressed as the sum of the Xth powers
+ * of unique, natural numbers.
+ * For example, if N=100 and X=3, we have to find all combinations of unique cubes adding up to 100.
+ * The only solution is 1^3 + 2^3 + 3^3 + 4^3. Therefore, the output will be 1.
+ *
+ * N is represented by the parameter 'targetSum' in the code.
+ * X is represented by the parameter 'power' in the code.
*/
public class PowerSum {
- private int count = 0;
- private int sum = 0;
-
- public int powSum(int n, int x) {
- sum(n, x, 1);
- return count;
+ /**
+ * Calculates the number of ways to express the target sum as a sum of Xth powers of unique natural numbers.
+ *
+ * @param targetSum The target sum to achieve (N in the problem statement)
+ * @param power The power to raise natural numbers to (X in the problem statement)
+ * @return The number of ways to express the target sum
+ */
+ public int powSum(int targetSum, int power) {
+ // Special case: when both targetSum and power are zero
+ if (targetSum == 0 && power == 0) {
+ return 1; // by convention, one way to sum to zero: use nothing
+ }
+ return sumRecursive(targetSum, power, 1, 0);
}
- // here i is the natural number which will be raised by X and added in sum.
- public void sum(int n, int x, int i) {
- // if sum is equal to N that is one of our answer and count is increased.
- if (sum == n) {
- count++;
- return;
- } // we will be adding next natural number raised to X only if on adding it in sum the
- // result is less than N.
- else if (sum + power(i, x) <= n) {
- sum += power(i, x);
- sum(n, x, i + 1);
- // backtracking and removing the number added last since no possible combination is
- // there with it.
- sum -= power(i, x);
+ /**
+ * Recursively calculates the number of ways to express the remaining sum as a sum of Xth powers.
+ *
+ * @param remainingSum The remaining sum to achieve
+ * @param power The power to raise natural numbers to (X in the problem statement)
+ * @param currentNumber The current natural number being considered
+ * @param currentSum The current sum of powered numbers
+ * @return The number of valid combinations
+ */
+ private int sumRecursive(int remainingSum, int power, int currentNumber, int currentSum) {
+ int newSum = currentSum + (int) Math.pow(currentNumber, power);
+
+ if (newSum == remainingSum) {
+ return 1;
}
- if (power(i, x) < n) {
- // calling the sum function with next natural number after backtracking if when it is
- // raised to X is still less than X.
- sum(n, x, i + 1);
+ if (newSum > remainingSum) {
+ return 0;
}
- }
- // creating a separate power function so that it can be used again and again when required.
- private int power(int a, int b) {
- return (int) Math.pow(a, b);
+ return sumRecursive(remainingSum, power, currentNumber + 1, newSum) + sumRecursive(remainingSum, power, currentNumber + 1, currentSum);
}
}
diff --git a/src/main/java/com/thealgorithms/backtracking/WordPatternMatcher.java b/src/main/java/com/thealgorithms/backtracking/WordPatternMatcher.java
new file mode 100644
index 000000000000..1854cab20a7f
--- /dev/null
+++ b/src/main/java/com/thealgorithms/backtracking/WordPatternMatcher.java
@@ -0,0 +1,86 @@
+package com.thealgorithms.backtracking;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Class to determine if a pattern matches a string using backtracking.
+ *
+ * Example:
+ * Pattern: "abab"
+ * Input String: "JavaPythonJavaPython"
+ * Output: true
+ *
+ * Pattern: "aaaa"
+ * Input String: "JavaJavaJavaJava"
+ * Output: true
+ *
+ * Pattern: "aabb"
+ * Input String: "JavaPythonPythonJava"
+ * Output: false
+ */
+public final class WordPatternMatcher {
+ private WordPatternMatcher() {
+ }
+
+ /**
+ * Determines if the given pattern matches the input string using backtracking.
+ *
+ * @param pattern The pattern to match.
+ * @param inputString The string to match against the pattern.
+ * @return True if the pattern matches the string, False otherwise.
+ */
+ public static boolean matchWordPattern(String pattern, String inputString) {
+ Map patternMap = new HashMap<>();
+ Map strMap = new HashMap<>();
+ return backtrack(pattern, inputString, 0, 0, patternMap, strMap);
+ }
+
+ /**
+ * Backtracking helper function to check if the pattern matches the string.
+ *
+ * @param pattern The pattern string.
+ * @param inputString The string to match against the pattern.
+ * @param patternIndex Current index in the pattern.
+ * @param strIndex Current index in the input string.
+ * @param patternMap Map to store pattern characters to string mappings.
+ * @param strMap Map to store string to pattern character mappings.
+ * @return True if the pattern matches, False otherwise.
+ */
+ private static boolean backtrack(String pattern, String inputString, int patternIndex, int strIndex, Map patternMap, Map strMap) {
+ if (patternIndex == pattern.length() && strIndex == inputString.length()) {
+ return true;
+ }
+ if (patternIndex == pattern.length() || strIndex == inputString.length()) {
+ return false;
+ }
+
+ char currentChar = pattern.charAt(patternIndex);
+ if (patternMap.containsKey(currentChar)) {
+ String mappedStr = patternMap.get(currentChar);
+ if (inputString.startsWith(mappedStr, strIndex)) {
+ return backtrack(pattern, inputString, patternIndex + 1, strIndex + mappedStr.length(), patternMap, strMap);
+ } else {
+ return false;
+ }
+ }
+
+ for (int end = strIndex + 1; end <= inputString.length(); end++) {
+ String substring = inputString.substring(strIndex, end);
+ if (strMap.containsKey(substring)) {
+ continue;
+ }
+
+ patternMap.put(currentChar, substring);
+ strMap.put(substring, currentChar);
+ if (backtrack(pattern, inputString, patternIndex + 1, end, patternMap, strMap)) {
+ return true;
+ }
+
+ patternMap.remove(currentChar);
+ strMap.remove(substring);
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/backtracking/WordSearch.java b/src/main/java/com/thealgorithms/backtracking/WordSearch.java
index f3a5b0433727..174ca90ccaab 100644
--- a/src/main/java/com/thealgorithms/backtracking/WordSearch.java
+++ b/src/main/java/com/thealgorithms/backtracking/WordSearch.java
@@ -1,35 +1,39 @@
package com.thealgorithms.backtracking;
-/*
-Word Search Problem (https://en.wikipedia.org/wiki/Word_search)
-
-Given an m x n grid of characters board and a string word, return true if word exists in the grid.
-
-The word can be constructed from letters of sequentially adjacent cell, where "adjacent" cells are
-those horizontally or vertically neighboring. The same letter cell may not be used more than once.
-
-For example,
-Given board =
-
-[
- ['A','B','C','E'],
- ['S','F','C','S'],
- ['A','D','E','E']
-]
-word = "ABCCED", -> returns true,
-word = "SEE", -> returns true,
-word = "ABCB", -> returns false.
-*/
-
-/*
- Solution
- Depth First Search in matrix (as multiple sources possible) with backtracking
- like finding cycle in a directed graph. Maintain a record of path
-
- Tx = O(m * n * 3^L): for each cell, we look at 3 options (not 4 as that one will be visited), we
- do it L times Sx = O(L) : stack size is max L
-*/
-
+/**
+ * Word Search Problem
+ *
+ * This class solves the word search problem where given an m x n grid of characters (board)
+ * and a target word, the task is to check if the word exists in the grid.
+ * The word can be constructed from sequentially adjacent cells (horizontally or vertically),
+ * and the same cell may not be used more than once in constructing the word.
+ *
+ * Example:
+ * - For board =
+ * [
+ * ['A','B','C','E'],
+ * ['S','F','C','S'],
+ * ['A','D','E','E']
+ * ]
+ * and word = "ABCCED", -> returns true
+ * and word = "SEE", -> returns true
+ * and word = "ABCB", -> returns false
+ *
+ * Solution:
+ * - Depth First Search (DFS) with backtracking is used to explore possible paths from any cell
+ * matching the first letter of the word. DFS ensures that we search all valid paths, while
+ * backtracking helps in reverting decisions when a path fails to lead to a solution.
+ *
+ * Time Complexity: O(m * n * 3^L)
+ * - m = number of rows in the board
+ * - n = number of columns in the board
+ * - L = length of the word
+ * - For each cell, we look at 3 possible directions (since we exclude the previously visited direction),
+ * and we do this for L letters.
+ *
+ * Space Complexity: O(L)
+ * - Stack space for the recursive DFS function, where L is the maximum depth of recursion (length of the word).
+ */
public class WordSearch {
private final int[] dx = {0, 0, 1, -1};
private final int[] dy = {1, -1, 0, 0};
@@ -37,15 +41,32 @@ public class WordSearch {
private char[][] board;
private String word;
+ /**
+ * Checks if the given (x, y) coordinates are valid positions in the board.
+ *
+ * @param x The row index.
+ * @param y The column index.
+ * @return True if the coordinates are within the bounds of the board; false otherwise.
+ */
private boolean isValid(int x, int y) {
return x >= 0 && x < board.length && y >= 0 && y < board[0].length;
}
+ /**
+ * Performs Depth First Search (DFS) from the cell (x, y)
+ * to search for the next character in the word.
+ *
+ * @param x The current row index.
+ * @param y The current column index.
+ * @param nextIdx The index of the next character in the word to be matched.
+ * @return True if a valid path is found to match the remaining characters of the word; false otherwise.
+ */
private boolean doDFS(int x, int y, int nextIdx) {
visited[x][y] = true;
if (nextIdx == word.length()) {
return true;
}
+
for (int i = 0; i < 4; ++i) {
int xi = x + dx[i];
int yi = y + dy[i];
@@ -56,10 +77,19 @@ private boolean doDFS(int x, int y, int nextIdx) {
}
}
}
- visited[x][y] = false;
+
+ visited[x][y] = false; // Backtrack
return false;
}
+ /**
+ * Main function to check if the word exists in the board. It initiates DFS from any
+ * cell that matches the first character of the word.
+ *
+ * @param board The 2D grid of characters (the board).
+ * @param word The target word to search for in the board.
+ * @return True if the word exists in the board; false otherwise.
+ */
public boolean exist(char[][] board, String word) {
this.board = board;
this.word = word;
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/BcdConversion.java b/src/main/java/com/thealgorithms/bitmanipulation/BcdConversion.java
new file mode 100644
index 000000000000..e6bd35720d9f
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/BcdConversion.java
@@ -0,0 +1,82 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * This class provides methods to convert between BCD (Binary-Coded Decimal) and decimal numbers.
+ *
+ * BCD is a class of binary encodings of decimal numbers where each decimal digit is represented by a fixed number of binary digits, usually four or eight.
+ *
+ * For more information, refer to the
+ * Binary-Coded Decimal Wikipedia page.
+ *
+ * Example usage:
+ *
+ * int decimal = BcdConversion.bcdToDecimal(0x1234);
+ * System.out.println("BCD 0x1234 to decimal: " + decimal); // Output: 1234
+ *
+ * int bcd = BcdConversion.decimalToBcd(1234);
+ * System.out.println("Decimal 1234 to BCD: " + Integer.toHexString(bcd)); // Output: 0x1234
+ *
+ */
+public final class BcdConversion {
+ private BcdConversion() {
+ }
+
+ /**
+ * Converts a BCD (Binary-Coded Decimal) number to a decimal number.
+ * Steps:
+ *
1. Validate the BCD number to ensure all digits are between 0 and 9.
+ *
2. Extract the last 4 bits (one BCD digit) from the BCD number.
+ *
3. Multiply the extracted digit by the corresponding power of 10 and add it to the decimal number.
+ *
4. Shift the BCD number right by 4 bits to process the next BCD digit.
+ *
5. Repeat steps 1-4 until the BCD number is zero.
+ *
+ * @param bcd The BCD number.
+ * @return The corresponding decimal number.
+ * @throws IllegalArgumentException if the BCD number contains invalid digits.
+ */
+ public static int bcdToDecimal(int bcd) {
+ int decimal = 0;
+ int multiplier = 1;
+
+ // Validate BCD digits
+ while (bcd > 0) {
+ int digit = bcd & 0xF;
+ if (digit > 9) {
+ throw new IllegalArgumentException("Invalid BCD digit: " + digit);
+ }
+ decimal += digit * multiplier;
+ multiplier *= 10;
+ bcd >>= 4;
+ }
+ return decimal;
+ }
+
+ /**
+ * Converts a decimal number to BCD (Binary-Coded Decimal).
+ *
Steps:
+ *
1. Check if the decimal number is within the valid range for BCD (0 to 9999).
+ *
2. Extract the last decimal digit from the decimal number.
+ *
3. Shift the digit to the correct BCD position and add it to the BCD number.
+ *
4. Remove the last decimal digit from the decimal number.
+ *
5. Repeat steps 2-4 until the decimal number is zero.
+ *
+ * @param decimal The decimal number.
+ * @return The corresponding BCD number.
+ * @throws IllegalArgumentException if the decimal number is greater than 9999.
+ */
+ public static int decimalToBcd(int decimal) {
+ if (decimal < 0 || decimal > 9999) {
+ throw new IllegalArgumentException("Value out of bounds for BCD representation: " + decimal);
+ }
+
+ int bcd = 0;
+ int shift = 0;
+ while (decimal > 0) {
+ int digit = decimal % 10;
+ bcd |= (digit << (shift * 4));
+ decimal /= 10;
+ shift++;
+ }
+ return bcd;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java b/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java
new file mode 100644
index 000000000000..0d6fd140c720
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java
@@ -0,0 +1,43 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * This class contains a method to check if the binary representation of a number is a palindrome.
+ *
+ * A binary palindrome is a number whose binary representation is the same when read from left to right and right to left.
+ * For example, the number 9 has a binary representation of 1001, which is a palindrome.
+ * The number 10 has a binary representation of 1010, which is not a palindrome.
+ *
+ *
+ * @author Hardvan
+ */
+public final class BinaryPalindromeCheck {
+ private BinaryPalindromeCheck() {
+ }
+
+ /**
+ * Checks if the binary representation of a number is a palindrome.
+ *
+ * @param x The number to check.
+ * @return True if the binary representation is a palindrome, otherwise false.
+ */
+ public static boolean isBinaryPalindrome(int x) {
+ int reversed = reverseBits(x);
+ return x == reversed;
+ }
+
+ /**
+ * Helper function to reverse all the bits of an integer.
+ *
+ * @param x The number to reverse the bits of.
+ * @return The number with reversed bits.
+ */
+ private static int reverseBits(int x) {
+ int result = 0;
+ while (x > 0) {
+ result <<= 1;
+ result |= (x & 1);
+ x >>= 1;
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/BooleanAlgebraGates.java b/src/main/java/com/thealgorithms/bitmanipulation/BooleanAlgebraGates.java
new file mode 100644
index 000000000000..869466320831
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/BooleanAlgebraGates.java
@@ -0,0 +1,111 @@
+package com.thealgorithms.bitmanipulation;
+
+import java.util.List;
+
+/**
+ * Implements various Boolean algebra gates (AND, OR, NOT, XOR, NAND, NOR)
+ */
+public final class BooleanAlgebraGates {
+
+ private BooleanAlgebraGates() {
+ // Prevent instantiation
+ }
+
+ /**
+ * Represents a Boolean gate that takes multiple inputs and returns a result.
+ */
+ interface BooleanGate {
+ /**
+ * Evaluates the gate with the given inputs.
+ *
+ * @param inputs The input values for the gate.
+ * @return The result of the evaluation.
+ */
+ boolean evaluate(List inputs);
+ }
+
+ /**
+ * AND Gate implementation.
+ * Returns true if all inputs are true; otherwise, false.
+ */
+ static class ANDGate implements BooleanGate {
+ @Override
+ public boolean evaluate(List inputs) {
+ for (boolean input : inputs) {
+ if (!input) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * OR Gate implementation.
+ * Returns true if at least one input is true; otherwise, false.
+ */
+ static class ORGate implements BooleanGate {
+ @Override
+ public boolean evaluate(List inputs) {
+ for (boolean input : inputs) {
+ if (input) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * NOT Gate implementation (Unary operation).
+ * Negates a single input value.
+ */
+ static class NOTGate {
+ /**
+ * Evaluates the negation of the input.
+ *
+ * @param input The input value to be negated.
+ * @return The negated value.
+ */
+ public boolean evaluate(boolean input) {
+ return !input;
+ }
+ }
+
+ /**
+ * XOR Gate implementation.
+ * Returns true if an odd number of inputs are true; otherwise, false.
+ */
+ static class XORGate implements BooleanGate {
+ @Override
+ public boolean evaluate(List inputs) {
+ boolean result = false;
+ for (boolean input : inputs) {
+ result ^= input;
+ }
+ return result;
+ }
+ }
+
+ /**
+ * NAND Gate implementation.
+ * Returns true if at least one input is false; otherwise, false.
+ */
+ static class NANDGate implements BooleanGate {
+ @Override
+ public boolean evaluate(List inputs) {
+ return !new ANDGate().evaluate(inputs); // Equivalent to negation of AND
+ }
+ }
+
+ /**
+ * NOR Gate implementation.
+ * Returns true if all inputs are false; otherwise, false.
+ */
+ static class NORGate implements BooleanGate {
+ @Override
+ public boolean evaluate(List inputs) {
+ return !new ORGate().evaluate(inputs); // Equivalent to negation of OR
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/ClearLeftmostSetBit.java b/src/main/java/com/thealgorithms/bitmanipulation/ClearLeftmostSetBit.java
new file mode 100644
index 000000000000..3e9a4a21183f
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/ClearLeftmostSetBit.java
@@ -0,0 +1,39 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * ClearLeftmostSetBit class contains a method to clear the leftmost set bit of a number.
+ * The leftmost set bit is the leftmost bit that is set to 1 in the binary representation of a number.
+ *
+ * Example:
+ * 26 (11010) -> 10 (01010)
+ * 1 (1) -> 0 (0)
+ * 7 (111) -> 3 (011)
+ * 6 (0110) -> 2 (0010)
+ *
+ * @author Hardvan
+ */
+public final class ClearLeftmostSetBit {
+ private ClearLeftmostSetBit() {
+ }
+
+ /**
+ * Clears the leftmost set bit (1) of a given number.
+ * Step 1: Find the position of the leftmost set bit
+ * Step 2: Create a mask with all bits set except for the leftmost set bit
+ * Step 3: Clear the leftmost set bit using AND with the mask
+ *
+ * @param num The input number.
+ * @return The number after clearing the leftmost set bit.
+ */
+ public static int clearLeftmostSetBit(int num) {
+ int pos = 0;
+ int temp = num;
+ while (temp > 0) {
+ temp >>= 1;
+ pos++;
+ }
+
+ int mask = ~(1 << (pos - 1));
+ return num & mask;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/CountLeadingZeros.java b/src/main/java/com/thealgorithms/bitmanipulation/CountLeadingZeros.java
new file mode 100644
index 000000000000..318334f0b951
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/CountLeadingZeros.java
@@ -0,0 +1,39 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * CountLeadingZeros class contains a method to count the number of leading zeros in the binary representation of a number.
+ * The number of leading zeros is the number of zeros before the leftmost 1 bit.
+ * For example, the number 5 has 29 leading zeros in its 32-bit binary representation.
+ * The number 0 has 32 leading zeros.
+ * The number 1 has 31 leading zeros.
+ * The number -1 has no leading zeros.
+ *
+ * @author Hardvan
+ */
+public final class CountLeadingZeros {
+ private CountLeadingZeros() {
+ }
+
+ /**
+ * Counts the number of leading zeros in the binary representation of a number.
+ * Method: Keep shifting the mask to the right until the leftmost bit is 1.
+ * The number of shifts is the number of leading zeros.
+ *
+ * @param num The input number.
+ * @return The number of leading zeros.
+ */
+ public static int countLeadingZeros(int num) {
+ if (num == 0) {
+ return 32;
+ }
+
+ int count = 0;
+ int mask = 1 << 31;
+ while ((mask & num) == 0) {
+ count++;
+ mask >>>= 1;
+ }
+
+ return count;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/others/CountSetBits.java b/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java
similarity index 64%
rename from src/main/java/com/thealgorithms/others/CountSetBits.java
rename to src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java
index b26f745d4cd7..242f35fc35f2 100644
--- a/src/main/java/com/thealgorithms/others/CountSetBits.java
+++ b/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.others;
+package com.thealgorithms.bitmanipulation;
public class CountSetBits {
@@ -48,4 +48,32 @@ public long countSetBits(long num) {
}
return cnt;
}
+
+ /**
+ * This approach takes O(1) running time to count the set bits, but requires a pre-processing.
+ *
+ * So, we divide our 32-bit input into 8-bit chunks, with four chunks. We have 8 bits in each chunk.
+ *
+ * Then the range is from 0-255 (0 to 2^7).
+ * So, we may need to count set bits from 0 to 255 in individual chunks.
+ *
+ * @param num takes a long number
+ * @return the count of set bits in the binary equivalent
+ */
+ public int lookupApproach(int num) {
+ int[] table = new int[256];
+ table[0] = 0;
+
+ for (int i = 1; i < 256; i++) {
+ table[i] = (i & 1) + table[i >> 1]; // i >> 1 equals to i/2
+ }
+
+ int res = 0;
+ for (int i = 0; i < 4; i++) {
+ res += table[num & 0xff];
+ num >>= 8;
+ }
+
+ return res;
+ }
}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/FindNthBit.java b/src/main/java/com/thealgorithms/bitmanipulation/FindNthBit.java
new file mode 100644
index 000000000000..7a35fc3feebf
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/FindNthBit.java
@@ -0,0 +1,46 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * A utility class to find the Nth bit of a given number.
+ *
+ * This class provides a method to extract the value of the Nth bit (either 0 or 1)
+ * from the binary representation of a given integer.
+ *
+ *
Example:
+ *
{@code
+ * int result = FindNthBit.findNthBit(5, 2); // returns 0 as the 2nd bit of 5 (binary 101) is 0.
+ * }
+ *
+ * Author: Tuhinm2002
+ */
+public final class FindNthBit {
+
+ /**
+ * Private constructor to prevent instantiation.
+ *
+ *
This is a utility class, and it should not be instantiated.
+ * Attempting to instantiate this class will throw an UnsupportedOperationException.
+ */
+ private FindNthBit() {
+ throw new UnsupportedOperationException("Utility class");
+ }
+
+ /**
+ * Finds the value of the Nth bit of the given number.
+ *
+ *
This method uses bitwise operations to extract the Nth bit from the
+ * binary representation of the given integer.
+ *
+ * @param num the integer number whose Nth bit is to be found
+ * @param n the bit position (1-based) to retrieve
+ * @return the value of the Nth bit (0 or 1)
+ * @throws IllegalArgumentException if the bit position is less than 1
+ */
+ public static int findNthBit(int num, int n) {
+ if (n < 1) {
+ throw new IllegalArgumentException("Bit position must be greater than or equal to 1.");
+ }
+ // Shifting the number to the right by (n - 1) positions and checking the last bit
+ return (num & (1 << (n - 1))) >> (n - 1);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/FirstDifferentBit.java b/src/main/java/com/thealgorithms/bitmanipulation/FirstDifferentBit.java
new file mode 100644
index 000000000000..9a761c572e2c
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/FirstDifferentBit.java
@@ -0,0 +1,33 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * This class provides a method to find the first differing bit
+ * between two integers.
+ *
+ * Example:
+ * x = 10 (1010 in binary)
+ * y = 12 (1100 in binary)
+ * The first differing bit is at index 1 (0-based)
+ * So, the output will be 1
+ *
+ * @author Hardvan
+ */
+public final class FirstDifferentBit {
+ private FirstDifferentBit() {
+ }
+
+ /**
+ * Identifies the index of the first differing bit between two integers.
+ * Steps:
+ * 1. XOR the two integers to get the differing bits
+ * 2. Find the index of the first set bit in XOR result
+ *
+ * @param x the first integer
+ * @param y the second integer
+ * @return the index of the first differing bit (0-based)
+ */
+ public static int firstDifferentBit(int x, int y) {
+ int diff = x ^ y;
+ return Integer.numberOfTrailingZeros(diff);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/GenerateSubsets.java b/src/main/java/com/thealgorithms/bitmanipulation/GenerateSubsets.java
new file mode 100644
index 000000000000..f1b812495c1b
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/GenerateSubsets.java
@@ -0,0 +1,44 @@
+package com.thealgorithms.bitmanipulation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides a method to generate all subsets (power set)
+ * of a given set using bit manipulation.
+ *
+ * @author Hardvan
+ */
+public final class GenerateSubsets {
+ private GenerateSubsets() {
+ }
+
+ /**
+ * Generates all subsets of a given set using bit manipulation.
+ * Steps:
+ * 1. Iterate over all numbers from 0 to 2^n - 1.
+ * 2. For each number, iterate over all bits from 0 to n - 1.
+ * 3. If the i-th bit of the number is set, add the i-th element of the set to the current subset.
+ * 4. Add the current subset to the list of subsets.
+ * 5. Return the list of subsets.
+ *
+ * @param set the input set of integers
+ * @return a list of all subsets represented as lists of integers
+ */
+ public static List> generateSubsets(int[] set) {
+ int n = set.length;
+ List> subsets = new ArrayList<>();
+
+ for (int mask = 0; mask < (1 << n); mask++) {
+ List subset = new ArrayList<>();
+ for (int i = 0; i < n; i++) {
+ if ((mask & (1 << i)) != 0) {
+ subset.add(set[i]);
+ }
+ }
+ subsets.add(subset);
+ }
+
+ return subsets;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/GrayCodeConversion.java b/src/main/java/com/thealgorithms/bitmanipulation/GrayCodeConversion.java
new file mode 100644
index 000000000000..83cd30c7d50a
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/GrayCodeConversion.java
@@ -0,0 +1,44 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * Gray code is a binary numeral system where two successive values differ in only one bit.
+ * This is a simple conversion between binary and Gray code.
+ * Example:
+ * 7 -> 0111 -> 0100 -> 4
+ * 4 -> 0100 -> 0111 -> 7
+ * 0 -> 0000 -> 0000 -> 0
+ * 1 -> 0001 -> 0000 -> 0
+ * 2 -> 0010 -> 0011 -> 3
+ * 3 -> 0011 -> 0010 -> 2
+ *
+ * @author Hardvan
+ */
+public final class GrayCodeConversion {
+ private GrayCodeConversion() {
+ }
+
+ /**
+ * Converts a binary number to Gray code.
+ *
+ * @param num The binary number.
+ * @return The corresponding Gray code.
+ */
+ public static int binaryToGray(int num) {
+ return num ^ (num >> 1);
+ }
+
+ /**
+ * Converts a Gray code number back to binary.
+ *
+ * @param gray The Gray code number.
+ * @return The corresponding binary number.
+ */
+ public static int grayToBinary(int gray) {
+ int binary = gray;
+ while (gray > 0) {
+ gray >>= 1;
+ binary ^= gray;
+ }
+ return binary;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/HammingDistance.java b/src/main/java/com/thealgorithms/bitmanipulation/HammingDistance.java
new file mode 100644
index 000000000000..4c24909ef234
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/HammingDistance.java
@@ -0,0 +1,29 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * The Hamming distance between two integers is the number of positions at which the corresponding bits are different.
+ * Given two integers x and y, calculate the Hamming distance.
+ * Example:
+ * Input: x = 1, y = 4
+ * Output: 2
+ * Explanation: 1 (0001) and 4 (0100) have 2 differing bits.
+ *
+ * @author Hardvan
+ */
+public final class HammingDistance {
+ private HammingDistance() {
+ }
+
+ /**
+ * Calculates the Hamming distance between two integers.
+ * The Hamming distance is the number of differing bits between the two integers.
+ *
+ * @param x The first integer.
+ * @param y The second integer.
+ * @return The Hamming distance (number of differing bits).
+ */
+ public static int hammingDistance(int x, int y) {
+ int xor = x ^ y;
+ return Integer.bitCount(xor);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/HigherLowerPowerOfTwo.java b/src/main/java/com/thealgorithms/bitmanipulation/HigherLowerPowerOfTwo.java
new file mode 100644
index 000000000000..0fb058b2b8a3
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/HigherLowerPowerOfTwo.java
@@ -0,0 +1,54 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * HigherLowerPowerOfTwo class has two methods to find the next higher and lower power of two.
+ *
+ * nextHigherPowerOfTwo method finds the next higher power of two.
+ * nextLowerPowerOfTwo method finds the next lower power of two.
+ * Both methods take an integer as input and return the next higher or lower power of two.
+ * If the input is less than 1, the next higher power of two is 1.
+ * If the input is less than or equal to 1, the next lower power of two is 0.
+ * nextHigherPowerOfTwo method uses bitwise operations to find the next higher power of two.
+ * nextLowerPowerOfTwo method uses Integer.highestOneBit method to find the next lower power of two.
+ * The time complexity of both methods is O(1).
+ * The space complexity of both methods is O(1).
+ *
+ *
+ * @author Hardvan
+ */
+public final class HigherLowerPowerOfTwo {
+ private HigherLowerPowerOfTwo() {
+ }
+
+ /**
+ * Finds the next higher power of two.
+ *
+ * @param x The given number.
+ * @return The next higher power of two.
+ */
+ public static int nextHigherPowerOfTwo(int x) {
+ if (x < 1) {
+ return 1;
+ }
+ x--;
+ x |= x >> 1;
+ x |= x >> 2;
+ x |= x >> 4;
+ x |= x >> 8;
+ x |= x >> 16;
+ return x + 1;
+ }
+
+ /**
+ * Finds the next lower power of two.
+ *
+ * @param x The given number.
+ * @return The next lower power of two.
+ */
+ public static int nextLowerPowerOfTwo(int x) {
+ if (x < 1) {
+ return 0;
+ }
+ return Integer.highestOneBit(x);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/HighestSetBit.java b/src/main/java/com/thealgorithms/bitmanipulation/HighestSetBit.java
index 6b53b1aa182b..2398b8214371 100644
--- a/src/main/java/com/thealgorithms/bitmanipulation/HighestSetBit.java
+++ b/src/main/java/com/thealgorithms/bitmanipulation/HighestSetBit.java
@@ -1,17 +1,39 @@
package com.thealgorithms.bitmanipulation;
+
import java.util.Optional;
/**
* Find Highest Set Bit
- * This class provides a function calculating the position (or index)
- * of the most significant bit being set to 1 in a given integer.
- * @author Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi)
+ *
+ * This class provides a utility method to calculate the position of the highest
+ * (most significant) bit that is set to 1 in a given non-negative integer.
+ * It is often used in bit manipulation tasks to find the left-most set bit in binary
+ * representation of a number.
+ *
+ * Example:
+ * - For input 18 (binary 10010), the highest set bit is at position 4 (zero-based index).
+ *
+ * @author Bama Charan Chhandogi
+ * @version 1.0
+ * @since 2021-06-23
*/
-
public final class HighestSetBit {
+
private HighestSetBit() {
}
+ /**
+ * Finds the highest (most significant) set bit in the given integer.
+ * The method returns the position (index) of the highest set bit as an {@link Optional}.
+ *
+ * - If the number is 0, no bits are set, and the method returns {@link Optional#empty()}.
+ * - If the number is negative, the method throws {@link IllegalArgumentException}.
+ *
+ * @param num The input integer for which the highest set bit is to be found. It must be non-negative.
+ * @return An {@link Optional} containing the index of the highest set bit (zero-based).
+ * Returns {@link Optional#empty()} if the number is 0.
+ * @throws IllegalArgumentException if the input number is negative.
+ */
public static Optional findHighestSetBit(int num) {
if (num < 0) {
throw new IllegalArgumentException("Input cannot be negative");
@@ -27,6 +49,6 @@ public static Optional findHighestSetBit(int num) {
position++;
}
- return Optional.of(position - 1);
+ return Optional.of(position - 1); // Subtract 1 to convert to zero-based index
}
}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/IndexOfRightMostSetBit.java b/src/main/java/com/thealgorithms/bitmanipulation/IndexOfRightMostSetBit.java
index b825916a8674..1b8962344ea7 100644
--- a/src/main/java/com/thealgorithms/bitmanipulation/IndexOfRightMostSetBit.java
+++ b/src/main/java/com/thealgorithms/bitmanipulation/IndexOfRightMostSetBit.java
@@ -1,13 +1,27 @@
package com.thealgorithms.bitmanipulation;
/**
- * Find The Index Of Right Most SetBit
- * @author Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi)
+ * Utility class for bit manipulation operations.
+ * This class provides methods to work with bitwise operations.
+ * Specifically, it includes a method to find the index of the rightmost set bit
+ * in an integer.
+ * This class is not meant to be instantiated.
+ *
+ * Author: Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi)
*/
-
public final class IndexOfRightMostSetBit {
+
private IndexOfRightMostSetBit() {
}
+
+ /**
+ * Finds the index of the rightmost set bit in the given integer.
+ * The index is zero-based, meaning the rightmost bit has an index of 0.
+ *
+ * @param n the integer to check for the rightmost set bit
+ * @return the index of the rightmost set bit; -1 if there are no set bits
+ * (i.e., the input integer is 0)
+ */
public static int indexOfRightMostSetBit(int n) {
if (n == 0) {
return -1; // No set bits
@@ -16,7 +30,7 @@ public static int indexOfRightMostSetBit(int n) {
// Handle negative numbers by finding the two's complement
if (n < 0) {
n = -n;
- n = n & (~n + 1); // Get the rightmost set bit in positive form
+ n = n & (~n + 1); // Isolate the rightmost set bit
}
int index = 0;
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/IsPowerTwo.java b/src/main/java/com/thealgorithms/bitmanipulation/IsPowerTwo.java
index 54d28d4d22cc..4cdf3c6faa3e 100644
--- a/src/main/java/com/thealgorithms/bitmanipulation/IsPowerTwo.java
+++ b/src/main/java/com/thealgorithms/bitmanipulation/IsPowerTwo.java
@@ -1,13 +1,27 @@
package com.thealgorithms.bitmanipulation;
/**
- * Is number power of 2
+ * Utility class for checking if a number is a power of two.
+ * A power of two is a number that can be expressed as 2^n where n is a non-negative integer.
+ * This class provides a method to determine if a given integer is a power of two using bit manipulation.
+ *
* @author Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi)
*/
-
public final class IsPowerTwo {
private IsPowerTwo() {
}
+
+ /**
+ * Checks if the given integer is a power of two.
+ *
+ * A number is considered a power of two if it is greater than zero and
+ * has exactly one '1' bit in its binary representation. This method
+ * uses the property that for any power of two (n), the expression
+ * (n & (n - 1)) will be zero.
+ *
+ * @param number the integer to check
+ * @return true if the number is a power of two, false otherwise
+ */
public static boolean isPowerTwo(int number) {
if (number <= 0) {
return false;
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/LowestSetBit.java b/src/main/java/com/thealgorithms/bitmanipulation/LowestSetBit.java
new file mode 100644
index 000000000000..127b6fa2c0b1
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/LowestSetBit.java
@@ -0,0 +1,34 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * Lowest Set Bit
+ * @author Prayas Kumar (https://github.com/prayas7102)
+ */
+
+public final class LowestSetBit {
+ // Private constructor to hide the default public one
+ private LowestSetBit() {
+ }
+ /**
+ * Isolates the lowest set bit of the given number. For example, if n = 18
+ * (binary: 10010), the result will be 2 (binary: 00010).
+ *
+ * @param n the number whose lowest set bit will be isolated
+ * @return the isolated lowest set bit of n
+ */
+ public static int isolateLowestSetBit(int n) {
+ // Isolate the lowest set bit using n & -n
+ return n & -n;
+ }
+ /**
+ * Clears the lowest set bit of the given number.
+ * For example, if n = 18 (binary: 10010), the result will be 16 (binary: 10000).
+ *
+ * @param n the number whose lowest set bit will be cleared
+ * @return the number after clearing its lowest set bit
+ */
+ public static int clearLowestSetBit(int n) {
+ // Clear the lowest set bit using n & (n - 1)
+ return n & (n - 1);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/ModuloPowerOfTwo.java b/src/main/java/com/thealgorithms/bitmanipulation/ModuloPowerOfTwo.java
new file mode 100644
index 000000000000..537a046f77e4
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/ModuloPowerOfTwo.java
@@ -0,0 +1,28 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * This class provides a method to compute the remainder
+ * of a number when divided by a power of two (2^n)
+ * without using division or modulo operations.
+ *
+ * @author Hardvan
+ */
+public final class ModuloPowerOfTwo {
+ private ModuloPowerOfTwo() {
+ }
+
+ /**
+ * Computes the remainder of a given integer when divided by 2^n.
+ *
+ * @param x the input number
+ * @param n the exponent (power of two)
+ * @return the remainder of x divided by 2^n
+ */
+ public static int moduloPowerOfTwo(int x, int n) {
+ if (n <= 0) {
+ throw new IllegalArgumentException("The exponent must be positive");
+ }
+
+ return x & ((1 << n) - 1);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/NextHigherSameBitCount.java b/src/main/java/com/thealgorithms/bitmanipulation/NextHigherSameBitCount.java
new file mode 100644
index 000000000000..6a764d806279
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/NextHigherSameBitCount.java
@@ -0,0 +1,30 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * This class provides a method to find the next higher number
+ * with the same number of set bits as the given number.
+ *
+ * @author Hardvan
+ */
+public final class NextHigherSameBitCount {
+ private NextHigherSameBitCount() {
+ }
+
+ /**
+ * Finds the next higher integer with the same number of set bits.
+ * Steps:
+ * 1. Find {@code c}, the rightmost set bit of {@code n}.
+ * 2. Find {@code r}, the rightmost set bit of {@code n + c}.
+ * 3. Swap the bits of {@code r} and {@code n} to the right of {@code c}.
+ * 4. Shift the bits of {@code r} and {@code n} to the right of {@code c} to the rightmost.
+ * 5. Combine the results of steps 3 and 4.
+ *
+ * @param n the input number
+ * @return the next higher integer with the same set bit count
+ */
+ public static int nextHigherSameBitCount(int n) {
+ int c = n & -n;
+ int r = n + c;
+ return (((r ^ n) >> 2) / c) | r;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinder.java b/src/main/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinder.java
index 07476a8b9476..17e1a73ec062 100644
--- a/src/main/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinder.java
+++ b/src/main/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinder.java
@@ -1,14 +1,30 @@
package com.thealgorithms.bitmanipulation;
/**
- * Find Non Repeating Number
+ * A utility class to find the non-repeating number in an array where every other number repeats.
+ * This class contains a method to identify the single unique number using bit manipulation.
+ *
+ * The solution leverages the properties of the XOR operation, which states that:
+ * - x ^ x = 0 for any integer x (a number XORed with itself is zero)
+ * - x ^ 0 = x for any integer x (a number XORed with zero is the number itself)
+ *
+ * Using these properties, we can find the non-repeating number in linear time with constant space.
+ *
+ * Example:
+ * Given the input array [2, 3, 5, 2, 3], the output will be 5 since it does not repeat.
+ *
* @author Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi)
*/
-
public final class NonRepeatingNumberFinder {
private NonRepeatingNumberFinder() {
}
+ /**
+ * Finds the non-repeating number in the given array.
+ *
+ * @param arr an array of integers where every number except one appears twice
+ * @return the integer that appears only once in the array or 0 if the array is empty
+ */
public static int findNonRepeatingNumber(int[] arr) {
int result = 0;
for (int num : arr) {
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/NumberAppearingOddTimes.java b/src/main/java/com/thealgorithms/bitmanipulation/NumberAppearingOddTimes.java
new file mode 100644
index 000000000000..bd4868d4dbd5
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/NumberAppearingOddTimes.java
@@ -0,0 +1,41 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * This class provides a method to find the element that appears an
+ * odd number of times in an array. All other elements in the array
+ * must appear an even number of times for the logic to work.
+ *
+ * The solution uses the XOR operation, which has the following properties:
+ * - a ^ a = 0 (XOR-ing the same numbers cancels them out)
+ * - a ^ 0 = a
+ * - XOR is commutative and associative.
+ *
+ * Time Complexity: O(n), where n is the size of the array.
+ * Space Complexity: O(1), as no extra space is used.
+ *
+ * Usage Example:
+ * int result = NumberAppearingOddTimes.findOddOccurrence(new int[]{1, 2, 1, 2, 3});
+ * // result will be 3
+ *
+ * @author Lakshyajeet Singh Goyal (https://github.com/DarkMatter-999)
+ */
+
+public final class NumberAppearingOddTimes {
+ private NumberAppearingOddTimes() {
+ }
+
+ /**
+ * Finds the element in the array that appears an odd number of times.
+ *
+ * @param arr the input array containing integers, where all elements
+ * except one appear an even number of times.
+ * @return the integer that appears an odd number of times.
+ */
+ public static int findOddOccurrence(int[] arr) {
+ int result = 0;
+ for (int num : arr) {
+ result ^= num;
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/NumbersDifferentSigns.java b/src/main/java/com/thealgorithms/bitmanipulation/NumbersDifferentSigns.java
index 8e0946f0eb23..a2da37aa81ee 100644
--- a/src/main/java/com/thealgorithms/bitmanipulation/NumbersDifferentSigns.java
+++ b/src/main/java/com/thealgorithms/bitmanipulation/NumbersDifferentSigns.java
@@ -1,14 +1,29 @@
package com.thealgorithms.bitmanipulation;
/**
- * Numbers Different Signs
+ * This class provides a method to determine whether two integers have
+ * different signs. It utilizes the XOR operation on the two numbers:
+ *
+ * - If two numbers have different signs, their most significant bits
+ * (sign bits) will differ, resulting in a negative XOR result.
+ * - If two numbers have the same sign, the XOR result will be non-negative.
+ *
+ * Time Complexity: O(1) - Constant time operation.
+ * Space Complexity: O(1) - No extra space used.
+ *
* @author Bama Charan Chhandogi
*/
-
public final class NumbersDifferentSigns {
private NumbersDifferentSigns() {
}
+ /**
+ * Determines if two integers have different signs using bitwise XOR.
+ *
+ * @param num1 the first integer
+ * @param num2 the second integer
+ * @return true if the two numbers have different signs, false otherwise
+ */
public static boolean differentSigns(int num1, int num2) {
return (num1 ^ num2) < 0;
}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/OneBitDifference.java b/src/main/java/com/thealgorithms/bitmanipulation/OneBitDifference.java
new file mode 100644
index 000000000000..afec0188e299
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/OneBitDifference.java
@@ -0,0 +1,32 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * This class provides a method to detect if two integers
+ * differ by exactly one bit flip.
+ *
+ * Example:
+ * 1 (0001) and 2 (0010) differ by exactly one bit flip.
+ * 7 (0111) and 3 (0011) differ by exactly one bit flip.
+ *
+ * @author Hardvan
+ */
+public final class OneBitDifference {
+ private OneBitDifference() {
+ }
+
+ /**
+ * Checks if two integers differ by exactly one bit.
+ *
+ * @param x the first integer
+ * @param y the second integer
+ * @return true if x and y differ by exactly one bit, false otherwise
+ */
+ public static boolean differByOneBit(int x, int y) {
+ if (x == y) {
+ return false;
+ }
+
+ int xor = x ^ y;
+ return (xor & (xor - 1)) == 0;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/OnesComplement.java b/src/main/java/com/thealgorithms/bitmanipulation/OnesComplement.java
new file mode 100644
index 000000000000..c5c068422113
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/OnesComplement.java
@@ -0,0 +1,28 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * @author - https://github.com/Monk-AbhinayVerma
+ * @Wikipedia - https://en.wikipedia.org/wiki/Ones%27_complement
+ * The class OnesComplement computes the complement of binary number
+ * and returns
+ * the complemented binary string.
+ * @return the complimented binary string
+ */
+public final class OnesComplement {
+ private OnesComplement() {
+ }
+
+ // Function to get the 1's complement of a binary number
+ public static String onesComplement(String binary) {
+ StringBuilder complement = new StringBuilder();
+ // Invert each bit to get the 1's complement
+ for (int i = 0; i < binary.length(); i++) {
+ if (binary.charAt(i) == '0') {
+ complement.append('1');
+ } else {
+ complement.append('0');
+ }
+ }
+ return complement.toString();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/ParityCheck.java b/src/main/java/com/thealgorithms/bitmanipulation/ParityCheck.java
new file mode 100644
index 000000000000..5acab4d4a362
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/ParityCheck.java
@@ -0,0 +1,34 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * The ParityCheck class provides a method to check the parity of a given number.
+ *
+ * Parity is a mathematical term that describes the property of an integer's binary representation.
+ * The parity of a binary number is the number of 1s in its binary representation.
+ * If the number of 1s is even, the parity is even; otherwise, it is odd.
+ *
+ * For example, the binary representation of 5 is 101, which has two 1s, so the parity of 5 is even.
+ * The binary representation of 6 is 110, which has two 1s, so the parity of 6 is even.
+ * The binary representation of 7 is 111, which has three 1s, so the parity of 7 is odd.
+ *
+ * @author Hardvan
+ */
+public final class ParityCheck {
+ private ParityCheck() {
+ }
+
+ /**
+ * This method checks the parity of the given number.
+ *
+ * @param n the number to check the parity of
+ * @return true if the number has even parity, false otherwise
+ */
+ public static boolean checkParity(int n) {
+ int count = 0;
+ while (n > 0) {
+ count += n & 1;
+ n >>= 1;
+ }
+ return count % 2 == 0;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/ReverseBits.java b/src/main/java/com/thealgorithms/bitmanipulation/ReverseBits.java
index e8f2930d3afe..12c269d9be48 100644
--- a/src/main/java/com/thealgorithms/bitmanipulation/ReverseBits.java
+++ b/src/main/java/com/thealgorithms/bitmanipulation/ReverseBits.java
@@ -1,14 +1,33 @@
package com.thealgorithms.bitmanipulation;
/**
- * Converts any Octal Number to a Binary Number
+ * This class provides a method to reverse the bits of a 32-bit integer.
+ * Reversing the bits means that the least significant bit (LSB) becomes
+ * the most significant bit (MSB) and vice versa.
+ *
+ * Example:
+ * Input (binary): 00000010100101000001111010011100 (43261596)
+ * Output (binary): 00111001011110000010100101000000 (964176192)
+ *
+ * Time Complexity: O(32) - A fixed number of 32 iterations
+ * Space Complexity: O(1) - No extra space used
+ *
+ * Note:
+ * - If the input is negative, Java handles it using two’s complement representation.
+ * - This function works on 32-bit integers by default.
+ *
* @author Bama Charan Chhandogi
*/
-
public final class ReverseBits {
private ReverseBits() {
}
+ /**
+ * Reverses the bits of a 32-bit integer.
+ *
+ * @param n the integer whose bits are to be reversed
+ * @return the integer obtained by reversing the bits of the input
+ */
public static int reverseBits(int n) {
int result = 0;
int bitCount = 32;
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/SingleBitOperations.java b/src/main/java/com/thealgorithms/bitmanipulation/SingleBitOperations.java
index b41aeca19af6..624a4e2b858a 100644
--- a/src/main/java/com/thealgorithms/bitmanipulation/SingleBitOperations.java
+++ b/src/main/java/com/thealgorithms/bitmanipulation/SingleBitOperations.java
@@ -1,34 +1,68 @@
package com.thealgorithms.bitmanipulation;
-/*
+/**
+ * A utility class for performing single-bit operations on integers.
+ * These operations include flipping, setting, clearing, and getting
+ * individual bits at specified positions.
+ *
+ * Bit positions are zero-indexed (i.e., the least significant bit is at position 0).
+ * These methods leverage bitwise operations for optimal performance.
+ *
+ * Examples:
+ * - `flipBit(3, 1)` flips the bit at index 1 in binary `11` (result: `1`).
+ * - `setBit(4, 0)` sets the bit at index 0 in `100` (result: `101` or 5).
+ * - `clearBit(7, 1)` clears the bit at index 1 in `111` (result: `101` or 5).
+ * - `getBit(6, 0)` checks if the least significant bit is set (result: `0`).
+ *
+ * Time Complexity: O(1) for all operations.
+ *
* Author: lukasb1b (https://github.com/lukasb1b)
*/
-
public final class SingleBitOperations {
private SingleBitOperations() {
}
+
/**
- * Flip the bit at position 'bit' in 'num'
+ * Flips (toggles) the bit at the specified position.
+ *
+ * @param num the input number
+ * @param bit the position of the bit to flip (0-indexed)
+ * @return the new number after flipping the specified bit
*/
public static int flipBit(final int num, final int bit) {
return num ^ (1 << bit);
}
+
/**
- * Set the bit at position 'bit' to 1 in the 'num' variable
+ * Sets the bit at the specified position to 1.
+ *
+ * @param num the input number
+ * @param bit the position of the bit to set (0-indexed)
+ * @return the new number after setting the specified bit to 1
*/
public static int setBit(final int num, final int bit) {
return num | (1 << bit);
}
+
/**
- * Clears the bit located at 'bit' from 'num'
+ * Clears the bit at the specified position (sets it to 0).
+ *
+ * @param num the input number
+ * @param bit the position of the bit to clear (0-indexed)
+ * @return the new number after clearing the specified bit
*/
public static int clearBit(final int num, final int bit) {
return num & ~(1 << bit);
}
+
/**
- * Get the bit located at 'bit' from 'num'
+ * Gets the bit value (0 or 1) at the specified position.
+ *
+ * @param num the input number
+ * @param bit the position of the bit to retrieve (0-indexed)
+ * @return 1 if the bit is set, 0 otherwise
*/
public static int getBit(final int num, final int bit) {
- return ((num >> bit) & 1);
+ return (num >> bit) & 1;
}
}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/SingleElement.java b/src/main/java/com/thealgorithms/bitmanipulation/SingleElement.java
new file mode 100644
index 000000000000..85ebdf02db25
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/SingleElement.java
@@ -0,0 +1,39 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * Utility class to find the single non-duplicate element from an array
+ * where all other elements appear twice.
+ *
+ * The algorithm runs in O(n) time complexity and O(1) space complexity
+ * using bitwise XOR.
+ *
+ *
+ * @author Tuhin M
+ */
+public final class SingleElement {
+
+ /**
+ * Private constructor to prevent instantiation of this utility class.
+ * Throws an UnsupportedOperationException if attempted.
+ */
+ private SingleElement() {
+ throw new UnsupportedOperationException("Utility Class");
+ }
+
+ /**
+ * Finds the single non-duplicate element in an array where every other
+ * element appears exactly twice. Uses bitwise XOR to achieve O(n) time
+ * complexity and O(1) space complexity.
+ *
+ * @param arr the input array containing integers where every element
+ * except one appears exactly twice
+ * @return the single non-duplicate element
+ */
+ public static int findSingleElement(int[] arr) {
+ int ele = 0;
+ for (int i = 0; i < arr.length; i++) {
+ ele ^= arr[i];
+ }
+ return ele;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/SwapAdjacentBits.java b/src/main/java/com/thealgorithms/bitmanipulation/SwapAdjacentBits.java
new file mode 100644
index 000000000000..98a7de8bdf1a
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/SwapAdjacentBits.java
@@ -0,0 +1,57 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * A utility class to swap every pair of adjacent bits in a given integer.
+ * This operation shifts the even-positioned bits to odd positions and vice versa.
+ *
+ * Example:
+ * - Input: 2 (binary: `10`) → Output: 1 (binary: `01`)
+ * - Input: 43 (binary: `101011`) → Output: 23 (binary: `010111`)
+ *
+ * **Explanation of the Algorithm:**
+ * 1. Mask even-positioned bits: Using `0xAAAAAAAA` (binary: `101010...`),
+ * which selects bits in even positions.
+ * 2. Mask odd-positioned bits: Using `0x55555555` (binary: `010101...`),
+ * which selects bits in odd positions.
+ * 3. Shift bits:
+ * - Right-shift even-positioned bits by 1 to move them to odd positions.
+ * - Left-shift odd-positioned bits by 1 to move them to even positions.
+ * 4. Combine both shifted results using bitwise OR (`|`) to produce the final result.
+ *
+ * Use Case: This algorithm can be useful in applications involving low-level bit manipulation,
+ * such as encoding, data compression, or cryptographic transformations.
+ *
+ * Time Complexity: O(1) (constant time, since operations are bitwise).
+ *
+ * Author: Lakshyajeet Singh Goyal (https://github.com/DarkMatter-999)
+ */
+public final class SwapAdjacentBits {
+ private SwapAdjacentBits() {
+ }
+
+ /**
+ * Swaps every pair of adjacent bits of a given integer.
+ * Steps:
+ * 1. Mask the even-positioned bits.
+ * 2. Mask the odd-positioned bits.
+ * 3. Shift the even bits to the right and the odd bits to the left.
+ * 4. Combine the shifted bits.
+ *
+ * @param num the integer whose bits are to be swapped
+ * @return the integer after swapping every pair of adjacent bits
+ */
+ public static int swapAdjacentBits(int num) {
+ // mask the even bits (0xAAAAAAAA => 10101010...)
+ int evenBits = num & 0xAAAAAAAA;
+
+ // mask the odd bits (0x55555555 => 01010101...)
+ int oddBits = num & 0x55555555;
+
+ // right shift even bits and left shift odd bits
+ evenBits >>= 1;
+ oddBits <<= 1;
+
+ // combine shifted bits
+ return evenBits | oddBits;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/TwosComplement.java b/src/main/java/com/thealgorithms/bitmanipulation/TwosComplement.java
new file mode 100644
index 000000000000..9b8cecd791a6
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/TwosComplement.java
@@ -0,0 +1,62 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * This class provides a method to compute the Two's Complement of a given binary number.
+ *
+ * In two's complement representation, a binary number's negative value is obtained
+ * by taking the one's complement (inverting all bits) and then adding 1 to the result.
+ * This method handles both small and large binary strings and ensures the output is
+ * correct for all binary inputs, including edge cases like all zeroes and all ones.
+ *
+ *
For more information on Two's Complement:
+ * @see Wikipedia - Two's Complement
+ *
+ *
Algorithm originally suggested by Jon von Neumann.
+ *
+ * @author Abhinay Verma (https://github.com/Monk-AbhinayVerma)
+ */
+public final class TwosComplement {
+ private TwosComplement() {
+ }
+
+ /**
+ * Computes the Two's Complement of the given binary string.
+ * Steps:
+ * 1. Compute the One's Complement (invert all bits).
+ * 2. Add 1 to the One's Complement to get the Two's Complement.
+ * 3. Iterate from the rightmost bit to the left, adding 1 and carrying over as needed.
+ * 4. If a carry is still present after the leftmost bit, prepend '1' to handle overflow.
+ *
+ * @param binary The binary number as a string (only '0' and '1' characters allowed).
+ * @return The two's complement of the input binary string as a new binary string.
+ * @throws IllegalArgumentException If the input contains non-binary characters.
+ */
+ public static String twosComplement(String binary) {
+ if (!binary.matches("[01]+")) {
+ throw new IllegalArgumentException("Input must contain only '0' and '1'.");
+ }
+
+ StringBuilder onesComplement = new StringBuilder();
+ for (char bit : binary.toCharArray()) {
+ onesComplement.append(bit == '0' ? '1' : '0');
+ }
+
+ StringBuilder twosComplement = new StringBuilder(onesComplement);
+ boolean carry = true;
+
+ for (int i = onesComplement.length() - 1; i >= 0 && carry; i--) {
+ if (onesComplement.charAt(i) == '1') {
+ twosComplement.setCharAt(i, '0');
+ } else {
+ twosComplement.setCharAt(i, '1');
+ carry = false;
+ }
+ }
+
+ if (carry) {
+ twosComplement.insert(0, '1');
+ }
+
+ return twosComplement.toString();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/Xs3Conversion.java b/src/main/java/com/thealgorithms/bitmanipulation/Xs3Conversion.java
new file mode 100644
index 000000000000..b22abc0c04ff
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/Xs3Conversion.java
@@ -0,0 +1,58 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * This class provides methods to convert between XS-3 (Excess-3) and binary.
+ *
+ * Excess-3, also called XS-3, is a binary-coded decimal (BCD) code in which each decimal digit is represented by its corresponding 4-bit binary value plus 3.
+ *
+ * For more information, refer to the
+ * Excess-3 Wikipedia page.
+ *
+ * Example usage:
+ *
+ * int binary = Xs3Conversion.xs3ToBinary(0x4567);
+ * System.out.println("XS-3 0x4567 to binary: " + binary); // Output: 1234
+ *
+ * int xs3 = Xs3Conversion.binaryToXs3(1234);
+ * System.out.println("Binary 1234 to XS-3: " + Integer.toHexString(xs3)); // Output: 0x4567
+ *
+ */
+public final class Xs3Conversion {
+ private Xs3Conversion() {
+ }
+ /**
+ * Converts an XS-3 (Excess-3) number to binary.
+ *
+ * @param xs3 The XS-3 number.
+ * @return The corresponding binary number.
+ */
+ public static int xs3ToBinary(int xs3) {
+ int binary = 0;
+ int multiplier = 1;
+ while (xs3 > 0) {
+ int digit = (xs3 & 0xF) - 3; // Extract the last 4 bits (one XS-3 digit) and subtract 3
+ binary += digit * multiplier;
+ multiplier *= 10;
+ xs3 >>= 4; // Shift right by 4 bits to process the next XS-3 digit
+ }
+ return binary;
+ }
+
+ /**
+ * Converts a binary number to XS-3 (Excess-3).
+ *
+ * @param binary The binary number.
+ * @return The corresponding XS-3 number.
+ */
+ public static int binaryToXs3(int binary) {
+ int xs3 = 0;
+ int shift = 0;
+ while (binary > 0) {
+ int digit = (binary % 10) + 3; // Extract the last decimal digit and add 3
+ xs3 |= (digit << (shift * 4)); // Shift the digit to the correct XS-3 position
+ binary /= 10; // Remove the last decimal digit
+ shift++;
+ }
+ return xs3;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/ciphers/ADFGVXCipher.java b/src/main/java/com/thealgorithms/ciphers/ADFGVXCipher.java
new file mode 100644
index 000000000000..d915858f9e6f
--- /dev/null
+++ b/src/main/java/com/thealgorithms/ciphers/ADFGVXCipher.java
@@ -0,0 +1,167 @@
+package com.thealgorithms.ciphers;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The ADFGVX cipher is a fractionating transposition cipher that was used by
+ * the German Army during World War I. It combines a **Polybius square substitution**
+ * with a **columnar transposition** to enhance encryption strength.
+ *
+ * The name "ADFGVX" refers to the six letters (A, D, F, G, V, X) used as row and
+ * column labels in the Polybius square. This cipher was designed to secure
+ * communication and create complex, hard-to-break ciphertexts.
+ *
+ * Learn more: ADFGVX Cipher - Wikipedia.
+ *
+ * Example usage:
+ *
+ * ADFGVXCipher cipher = new ADFGVXCipher();
+ * String encrypted = cipher.encrypt("attack at 1200am", "PRIVACY");
+ * String decrypted = cipher.decrypt(encrypted, "PRIVACY");
+ *
+ *
+ * @author bennybebo
+ */
+public class ADFGVXCipher {
+
+ // Constants used in the Polybius square
+ private static final char[] POLYBIUS_LETTERS = {'A', 'D', 'F', 'G', 'V', 'X'};
+ private static final char[][] POLYBIUS_SQUARE = {{'N', 'A', '1', 'C', '3', 'H'}, {'8', 'T', 'B', '2', 'O', 'M'}, {'E', '5', 'W', 'R', 'P', 'D'}, {'4', 'F', '6', 'G', '7', 'I'}, {'9', 'J', '0', 'K', 'L', 'Q'}, {'S', 'U', 'V', 'X', 'Y', 'Z'}};
+
+ // Maps for fast substitution lookups
+ private static final Map POLYBIUS_MAP = new HashMap<>();
+ private static final Map REVERSE_POLYBIUS_MAP = new HashMap<>();
+
+ // Static block to initialize the lookup tables from the Polybius square
+ static {
+ for (int i = 0; i < POLYBIUS_SQUARE.length; i++) {
+ for (int j = 0; j < POLYBIUS_SQUARE[i].length; j++) {
+ String key = "" + POLYBIUS_LETTERS[i] + POLYBIUS_LETTERS[j];
+ POLYBIUS_MAP.put(key, POLYBIUS_SQUARE[i][j]);
+ REVERSE_POLYBIUS_MAP.put(POLYBIUS_SQUARE[i][j], key);
+ }
+ }
+ }
+
+ /**
+ * Encrypts a given plaintext using the ADFGVX cipher with the provided keyword.
+ * Steps:
+ * 1. Substitute each letter in the plaintext with a pair of ADFGVX letters.
+ * 2. Perform a columnar transposition on the fractionated text using the keyword.
+ *
+ * @param plaintext The message to be encrypted (can contain letters and digits).
+ * @param key The keyword for columnar transposition.
+ * @return The encrypted message as ciphertext.
+ */
+ public String encrypt(String plaintext, String key) {
+ plaintext = plaintext.toUpperCase().replaceAll("[^A-Z0-9]", ""); // Sanitize input
+ StringBuilder fractionatedText = new StringBuilder();
+
+ for (char c : plaintext.toCharArray()) {
+ fractionatedText.append(REVERSE_POLYBIUS_MAP.get(c));
+ }
+
+ return columnarTransposition(fractionatedText.toString(), key);
+ }
+
+ /**
+ * Decrypts a given ciphertext using the ADFGVX cipher with the provided keyword.
+ * Steps:
+ * 1. Reverse the columnar transposition performed during encryption.
+ * 2. Substitute each pair of ADFGVX letters with the corresponding plaintext letter.
+ * The resulting text is the decrypted message.
+ *
+ * @param ciphertext The encrypted message.
+ * @param key The keyword used during encryption.
+ * @return The decrypted plaintext message.
+ */
+ public String decrypt(String ciphertext, String key) {
+ String fractionatedText = reverseColumnarTransposition(ciphertext, key);
+
+ StringBuilder plaintext = new StringBuilder();
+ for (int i = 0; i < fractionatedText.length(); i += 2) {
+ String pair = fractionatedText.substring(i, i + 2);
+ plaintext.append(POLYBIUS_MAP.get(pair));
+ }
+
+ return plaintext.toString();
+ }
+
+ /**
+ * Helper method: Performs columnar transposition during encryption
+ *
+ * @param text The fractionated text to be transposed
+ * @param key The keyword for columnar transposition
+ * @return The transposed text
+ */
+ private String columnarTransposition(String text, String key) {
+ int numRows = (int) Math.ceil((double) text.length() / key.length());
+ char[][] table = new char[numRows][key.length()];
+ for (char[] row : table) { // Fill empty cells with underscores
+ Arrays.fill(row, '_');
+ }
+
+ // Populate the table row by row
+ for (int i = 0; i < text.length(); i++) {
+ table[i / key.length()][i % key.length()] = text.charAt(i);
+ }
+
+ // Read columns based on the alphabetical order of the key
+ StringBuilder ciphertext = new StringBuilder();
+ char[] sortedKey = key.toCharArray();
+ Arrays.sort(sortedKey);
+
+ for (char keyChar : sortedKey) {
+ int column = key.indexOf(keyChar);
+ for (char[] row : table) {
+ if (row[column] != '_') {
+ ciphertext.append(row[column]);
+ }
+ }
+ }
+
+ return ciphertext.toString();
+ }
+
+ /**
+ * Helper method: Reverses the columnar transposition during decryption
+ *
+ * @param ciphertext The transposed text to be reversed
+ * @param key The keyword used during encryption
+ * @return The reversed text
+ */
+ private String reverseColumnarTransposition(String ciphertext, String key) {
+ int numRows = (int) Math.ceil((double) ciphertext.length() / key.length());
+ char[][] table = new char[numRows][key.length()];
+
+ char[] sortedKey = key.toCharArray();
+ Arrays.sort(sortedKey);
+
+ int index = 0;
+ // Populate the table column by column according to the sorted key
+ for (char keyChar : sortedKey) {
+ int column = key.indexOf(keyChar);
+ for (int row = 0; row < numRows; row++) {
+ if (index < ciphertext.length()) {
+ table[row][column] = ciphertext.charAt(index++);
+ } else {
+ table[row][column] = '_';
+ }
+ }
+ }
+
+ // Read the table row by row to reconstruct the fractionated text
+ StringBuilder fractionatedText = new StringBuilder();
+ for (char[] row : table) {
+ for (char cell : row) {
+ if (cell != '_') {
+ fractionatedText.append(cell);
+ }
+ }
+ }
+
+ return fractionatedText.toString();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/ciphers/AES.java b/src/main/java/com/thealgorithms/ciphers/AES.java
index 5d614afbe584..1c283f6b7655 100644
--- a/src/main/java/com/thealgorithms/ciphers/AES.java
+++ b/src/main/java/com/thealgorithms/ciphers/AES.java
@@ -2418,8 +2418,6 @@ public static BigInteger scheduleCore(BigInteger t, int rconCounter) {
rBytes = new StringBuilder(rBytes.substring(0, i * 2) + currentByteBits + rBytes.substring((i + 1) * 2));
}
- // t = new BigInteger(rBytes, 16);
- // return t;
return new BigInteger(rBytes.toString(), 16);
}
diff --git a/src/main/java/com/thealgorithms/ciphers/AffineCipher.java b/src/main/java/com/thealgorithms/ciphers/AffineCipher.java
index bcf3a5b0167b..b82681372f2b 100644
--- a/src/main/java/com/thealgorithms/ciphers/AffineCipher.java
+++ b/src/main/java/com/thealgorithms/ciphers/AffineCipher.java
@@ -1,5 +1,23 @@
package com.thealgorithms.ciphers;
+/**
+ * The AffineCipher class implements the Affine cipher, a type of monoalphabetic substitution cipher.
+ * It encrypts and decrypts messages using a linear transformation defined by the formula:
+ *
+ * E(x) = (a * x + b) mod m
+ * D(y) = a^-1 * (y - b) mod m
+ *
+ * where:
+ * - E(x) is the encrypted character,
+ * - D(y) is the decrypted character,
+ * - a is the multiplicative key (must be coprime to m),
+ * - b is the additive key,
+ * - x is the index of the plaintext character,
+ * - y is the index of the ciphertext character,
+ * - m is the size of the alphabet (26 for the English alphabet).
+ *
+ * The class provides methods for encrypting and decrypting messages, as well as a main method to demonstrate its usage.
+ */
final class AffineCipher {
private AffineCipher() {
}
@@ -8,51 +26,62 @@ private AffineCipher() {
static int a = 17;
static int b = 20;
+ /**
+ * Encrypts a message using the Affine cipher.
+ *
+ * @param msg the plaintext message as a character array
+ * @return the encrypted ciphertext
+ */
static String encryptMessage(char[] msg) {
- /// Cipher Text initially empty
- String cipher = "";
+ // Cipher Text initially empty
+ StringBuilder cipher = new StringBuilder();
for (int i = 0; i < msg.length; i++) {
// Avoid space to be encrypted
- /* applying encryption formula ( a x + b ) mod m
+ /* applying encryption formula ( a * x + b ) mod m
{here x is msg[i] and m is 26} and added 'A' to
- bring it in range of ascii alphabet[ 65-90 | A-Z ] */
+ bring it in the range of ASCII alphabet [65-90 | A-Z] */
if (msg[i] != ' ') {
- cipher = cipher + (char) ((((a * (msg[i] - 'A')) + b) % 26) + 'A');
+ cipher.append((char) ((((a * (msg[i] - 'A')) + b) % 26) + 'A'));
} else { // else simply append space character
- cipher += msg[i];
+ cipher.append(msg[i]);
}
}
- return cipher;
+ return cipher.toString();
}
+ /**
+ * Decrypts a ciphertext using the Affine cipher.
+ *
+ * @param cipher the ciphertext to decrypt
+ * @return the decrypted plaintext message
+ */
static String decryptCipher(String cipher) {
- String msg = "";
+ StringBuilder msg = new StringBuilder();
int aInv = 0;
- int flag = 0;
+ int flag;
- // Find a^-1 (the multiplicative inverse of a
- // in the group of integers modulo m.)
+ // Find a^-1 (the multiplicative inverse of a in the group of integers modulo m.)
for (int i = 0; i < 26; i++) {
flag = (a * i) % 26;
- // Check if (a*i)%26 == 1,
+ // Check if (a * i) % 26 == 1,
// then i will be the multiplicative inverse of a
if (flag == 1) {
aInv = i;
}
}
for (int i = 0; i < cipher.length(); i++) {
- /*Applying decryption formula a^-1 ( x - b ) mod m
+ /* Applying decryption formula a^-1 * (x - b) mod m
{here x is cipher[i] and m is 26} and added 'A'
- to bring it in range of ASCII alphabet[ 65-90 | A-Z ] */
+ to bring it in the range of ASCII alphabet [65-90 | A-Z] */
if (cipher.charAt(i) != ' ') {
- msg = msg + (char) (((aInv * ((cipher.charAt(i) + 'A' - b)) % 26)) + 'A');
+ msg.append((char) (((aInv * ((cipher.charAt(i) - 'A') - b + 26)) % 26) + 'A'));
} else { // else simply append space character
- msg += cipher.charAt(i);
+ msg.append(cipher.charAt(i));
}
}
- return msg;
+ return msg.toString();
}
// Driver code
diff --git a/src/main/java/com/thealgorithms/ciphers/AtbashCipher.java b/src/main/java/com/thealgorithms/ciphers/AtbashCipher.java
new file mode 100644
index 000000000000..9169aa82bd75
--- /dev/null
+++ b/src/main/java/com/thealgorithms/ciphers/AtbashCipher.java
@@ -0,0 +1,101 @@
+package com.thealgorithms.ciphers;
+
+/**
+ * The Atbash cipher is a classic substitution cipher that substitutes each letter
+ * with its opposite letter in the alphabet.
+ *
+ * For example:
+ * - 'A' becomes 'Z', 'B' becomes 'Y', 'C' becomes 'X', and so on.
+ * - Similarly, 'a' becomes 'z', 'b' becomes 'y', and so on.
+ *
+ * The cipher works identically for both uppercase and lowercase letters.
+ * Non-alphabetical characters remain unchanged in the output.
+ *
+ * This cipher is symmetric, meaning that applying the cipher twice will return
+ * the original text. Therefore, the same function is used for both encryption and decryption.
+ *
+ * Usage Example:
+ *
+ * AtbashCipher cipher = new AtbashCipher("Hello World!");
+ * String encrypted = cipher.convert(); // Output: "Svool Dliow!"
+ *
+ *
+ * @author Krounosity
+ * @see Atbash Cipher (Wikipedia)
+ */
+public class AtbashCipher {
+
+ private String toConvert;
+
+ public AtbashCipher() {
+ }
+
+ /**
+ * Constructor with a string parameter.
+ *
+ * @param str The string to be converted using the Atbash cipher
+ */
+ public AtbashCipher(String str) {
+ this.toConvert = str;
+ }
+
+ /**
+ * Returns the current string set for conversion.
+ *
+ * @return The string to be converted
+ */
+ public String getString() {
+ return toConvert;
+ }
+
+ /**
+ * Sets the string to be converted using the Atbash cipher.
+ *
+ * @param str The new string to convert
+ */
+ public void setString(String str) {
+ this.toConvert = str;
+ }
+
+ /**
+ * Checks if a character is uppercase.
+ *
+ * @param ch The character to check
+ * @return {@code true} if the character is uppercase, {@code false} otherwise
+ */
+ private boolean isCapital(char ch) {
+ return ch >= 'A' && ch <= 'Z';
+ }
+
+ /**
+ * Checks if a character is lowercase.
+ *
+ * @param ch The character to check
+ * @return {@code true} if the character is lowercase, {@code false} otherwise
+ */
+ private boolean isSmall(char ch) {
+ return ch >= 'a' && ch <= 'z';
+ }
+
+ /**
+ * Converts the input string using the Atbash cipher.
+ * Alphabetic characters are substituted with their opposite in the alphabet,
+ * while non-alphabetic characters remain unchanged.
+ *
+ * @return The converted string after applying the Atbash cipher
+ */
+ public String convert() {
+ StringBuilder convertedString = new StringBuilder();
+
+ for (char ch : toConvert.toCharArray()) {
+ if (isSmall(ch)) {
+ convertedString.append((char) ('z' - (ch - 'a')));
+ } else if (isCapital(ch)) {
+ convertedString.append((char) ('Z' - (ch - 'A')));
+ } else {
+ convertedString.append(ch);
+ }
+ }
+ return convertedString.toString();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/ciphers/Autokey.java b/src/main/java/com/thealgorithms/ciphers/Autokey.java
new file mode 100644
index 000000000000..bb67f512accf
--- /dev/null
+++ b/src/main/java/com/thealgorithms/ciphers/Autokey.java
@@ -0,0 +1,55 @@
+package com.thealgorithms.ciphers;
+
+/**
+ * The Autokey Cipher is an interesting and historically significant encryption method,
+ * as it improves upon the classic Vigenère Cipher by using the plaintext itself to
+ * extend the key. This makes it harder to break using frequency analysis, as it
+ * doesn’t rely solely on a repeated key.
+ * https://en.wikipedia.org/wiki/Autokey_cipher
+ *
+ * @author bennybebo
+ */
+public class Autokey {
+
+ // Encrypts the plaintext using the Autokey cipher
+ public String encrypt(String plaintext, String keyword) {
+ plaintext = plaintext.toUpperCase().replaceAll("[^A-Z]", ""); // Sanitize input
+ keyword = keyword.toUpperCase();
+
+ StringBuilder extendedKey = new StringBuilder(keyword);
+ extendedKey.append(plaintext); // Extend key with plaintext
+
+ StringBuilder ciphertext = new StringBuilder();
+
+ for (int i = 0; i < plaintext.length(); i++) {
+ char plainChar = plaintext.charAt(i);
+ char keyChar = extendedKey.charAt(i);
+
+ int encryptedChar = (plainChar - 'A' + keyChar - 'A') % 26 + 'A';
+ ciphertext.append((char) encryptedChar);
+ }
+
+ return ciphertext.toString();
+ }
+
+ // Decrypts the ciphertext using the Autokey cipher
+ public String decrypt(String ciphertext, String keyword) {
+ ciphertext = ciphertext.toUpperCase().replaceAll("[^A-Z]", ""); // Sanitize input
+ keyword = keyword.toUpperCase();
+
+ StringBuilder plaintext = new StringBuilder();
+ StringBuilder extendedKey = new StringBuilder(keyword);
+
+ for (int i = 0; i < ciphertext.length(); i++) {
+ char cipherChar = ciphertext.charAt(i);
+ char keyChar = extendedKey.charAt(i);
+
+ int decryptedChar = (cipherChar - 'A' - (keyChar - 'A') + 26) % 26 + 'A';
+ plaintext.append((char) decryptedChar);
+
+ extendedKey.append((char) decryptedChar); // Extend key with each decrypted char
+ }
+
+ return plaintext.toString();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/ciphers/BaconianCipher.java b/src/main/java/com/thealgorithms/ciphers/BaconianCipher.java
new file mode 100644
index 000000000000..16dfd6e674af
--- /dev/null
+++ b/src/main/java/com/thealgorithms/ciphers/BaconianCipher.java
@@ -0,0 +1,71 @@
+package com.thealgorithms.ciphers;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The Baconian Cipher is a substitution cipher where each letter is represented
+ * by a group of five binary digits (A's and B's). It can also be used to hide
+ * messages within other texts, making it a simple form of steganography.
+ * https://en.wikipedia.org/wiki/Bacon%27s_cipher
+ *
+ * @author Bennybebo
+ */
+public class BaconianCipher {
+
+ private static final Map BACONIAN_MAP = new HashMap<>();
+ private static final Map REVERSE_BACONIAN_MAP = new HashMap<>();
+
+ static {
+ // Initialize the Baconian cipher mappings
+ String[] baconianAlphabet = {"AAAAA", "AAAAB", "AAABA", "AAABB", "AABAA", "AABAB", "AABBA", "AABBB", "ABAAA", "ABAAB", "ABABA", "ABABB", "ABBAA", "ABBAB", "ABBBA", "ABBBB", "BAAAA", "BAAAB", "BAABA", "BAABB", "BABAA", "BABAB", "BABBA", "BABBB", "BBAAA", "BBAAB"};
+ char letter = 'A';
+ for (String code : baconianAlphabet) {
+ BACONIAN_MAP.put(letter, code);
+ REVERSE_BACONIAN_MAP.put(code, letter);
+ letter++;
+ }
+
+ // Handle I/J as the same letter
+ BACONIAN_MAP.put('I', BACONIAN_MAP.get('J'));
+ REVERSE_BACONIAN_MAP.put(BACONIAN_MAP.get('I'), 'I');
+ }
+
+ /**
+ * Encrypts the given plaintext using the Baconian cipher.
+ *
+ * @param plaintext The plaintext message to encrypt.
+ * @return The ciphertext as a binary (A/B) sequence.
+ */
+ public String encrypt(String plaintext) {
+ StringBuilder ciphertext = new StringBuilder();
+ plaintext = plaintext.toUpperCase().replaceAll("[^A-Z]", ""); // Remove non-letter characters
+
+ for (char letter : plaintext.toCharArray()) {
+ ciphertext.append(BACONIAN_MAP.get(letter));
+ }
+
+ return ciphertext.toString();
+ }
+
+ /**
+ * Decrypts the given ciphertext encoded in binary (A/B) format using the Baconian cipher.
+ *
+ * @param ciphertext The ciphertext to decrypt.
+ * @return The decrypted plaintext message.
+ */
+ public String decrypt(String ciphertext) {
+ StringBuilder plaintext = new StringBuilder();
+
+ for (int i = 0; i < ciphertext.length(); i += 5) {
+ String code = ciphertext.substring(i, i + 5);
+ if (REVERSE_BACONIAN_MAP.containsKey(code)) {
+ plaintext.append(REVERSE_BACONIAN_MAP.get(code));
+ } else {
+ throw new IllegalArgumentException("Invalid Baconian code: " + code);
+ }
+ }
+
+ return plaintext.toString();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/ciphers/Blowfish.java b/src/main/java/com/thealgorithms/ciphers/Blowfish.java
index f6a0a3753e9b..ea1807e62710 100644
--- a/src/main/java/com/thealgorithms/ciphers/Blowfish.java
+++ b/src/main/java/com/thealgorithms/ciphers/Blowfish.java
@@ -1078,7 +1078,7 @@ public class Blowfish {
* @return String object which is a binary representation of the hex number passed as parameter
*/
private String hexToBin(String hex) {
- String binary = "";
+ StringBuilder binary = new StringBuilder();
long num;
String binary4B;
int n = hex.length();
@@ -1089,9 +1089,9 @@ private String hexToBin(String hex) {
binary4B = "0000" + binary4B;
binary4B = binary4B.substring(binary4B.length() - 4);
- binary += binary4B;
+ binary.append(binary4B);
}
- return binary;
+ return binary.toString();
}
/**
@@ -1103,12 +1103,12 @@ private String hexToBin(String hex) {
*/
private String binToHex(String binary) {
long num = Long.parseUnsignedLong(binary, 2);
- String hex = Long.toHexString(num);
+ StringBuilder hex = new StringBuilder(Long.toHexString(num));
while (hex.length() < (binary.length() / 4)) {
- hex = "0" + hex;
+ hex.insert(0, "0");
}
- return hex;
+ return hex.toString();
}
/**
@@ -1121,12 +1121,12 @@ private String binToHex(String binary) {
private String xor(String a, String b) {
a = hexToBin(a);
b = hexToBin(b);
- String ans = "";
+ StringBuilder ans = new StringBuilder();
for (int i = 0; i < a.length(); i++) {
- ans += (char) (((a.charAt(i) - '0') ^ (b.charAt(i) - '0')) + '0');
+ ans.append((char) (((a.charAt(i) - '0') ^ (b.charAt(i) - '0')) + '0'));
}
- ans = binToHex(ans);
- return ans;
+ ans = new StringBuilder(binToHex(ans.toString()));
+ return ans.toString();
}
/**
diff --git a/src/main/java/com/thealgorithms/ciphers/Caesar.java b/src/main/java/com/thealgorithms/ciphers/Caesar.java
index 61c444cf6463..23535bc2b5d2 100644
--- a/src/main/java/com/thealgorithms/ciphers/Caesar.java
+++ b/src/main/java/com/thealgorithms/ciphers/Caesar.java
@@ -9,6 +9,9 @@
* @author khalil2535
*/
public class Caesar {
+ private static char normalizeShift(final int shift) {
+ return (char) (shift % 26);
+ }
/**
* Encrypt text by shifting every Latin char by add number shift for ASCII
@@ -19,7 +22,7 @@ public class Caesar {
public String encode(String message, int shift) {
StringBuilder encoded = new StringBuilder();
- shift %= 26;
+ final char shiftChar = normalizeShift(shift);
final int length = message.length();
for (int i = 0; i < length; i++) {
@@ -29,10 +32,10 @@ public String encode(String message, int shift) {
char current = message.charAt(i); // Java law : char + int = char
if (isCapitalLatinLetter(current)) {
- current += shift;
+ current += shiftChar;
encoded.append((char) (current > 'Z' ? current - 26 : current)); // 26 = number of latin letters
} else if (isSmallLatinLetter(current)) {
- current += shift;
+ current += shiftChar;
encoded.append((char) (current > 'z' ? current - 26 : current)); // 26 = number of latin letters
} else {
encoded.append(current);
@@ -50,16 +53,16 @@ public String encode(String message, int shift) {
public String decode(String encryptedMessage, int shift) {
StringBuilder decoded = new StringBuilder();
- shift %= 26;
+ final char shiftChar = normalizeShift(shift);
final int length = encryptedMessage.length();
for (int i = 0; i < length; i++) {
char current = encryptedMessage.charAt(i);
if (isCapitalLatinLetter(current)) {
- current -= shift;
+ current -= shiftChar;
decoded.append((char) (current < 'A' ? current + 26 : current)); // 26 = number of latin letters
} else if (isSmallLatinLetter(current)) {
- current -= shift;
+ current -= shiftChar;
decoded.append((char) (current < 'a' ? current + 26 : current)); // 26 = number of latin letters
} else {
decoded.append(current);
diff --git a/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java b/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java
index e59cfb12d816..b6b889b079ca 100644
--- a/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java
+++ b/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java
@@ -27,13 +27,13 @@ private ColumnarTranspositionCipher() {
* @return a String with the word encrypted by the Columnar Transposition
* Cipher Rule
*/
- public static String encrpyter(String word, String keyword) {
+ public static String encrypt(final String word, final String keyword) {
ColumnarTranspositionCipher.keyword = keyword;
- abecedariumBuilder(500);
+ abecedariumBuilder();
table = tableBuilder(word);
Object[][] sortedTable = sortTable(table);
StringBuilder wordEncrypted = new StringBuilder();
- for (int i = 0; i < sortedTable[i].length; i++) {
+ for (int i = 0; i < sortedTable[0].length; i++) {
for (int j = 1; j < sortedTable.length; j++) {
wordEncrypted.append(sortedTable[j][i]);
}
@@ -51,11 +51,12 @@ public static String encrpyter(String word, String keyword) {
* @return a String with the word encrypted by the Columnar Transposition
* Cipher Rule
*/
- public static String encrpyter(String word, String keyword, String abecedarium) {
+ public static String encrypt(String word, String keyword, String abecedarium) {
ColumnarTranspositionCipher.keyword = keyword;
ColumnarTranspositionCipher.abecedarium = Objects.requireNonNullElse(abecedarium, ABECEDARIUM);
table = tableBuilder(word);
Object[][] sortedTable = sortTable(table);
+
StringBuilder wordEncrypted = new StringBuilder();
for (int i = 0; i < sortedTable[0].length; i++) {
for (int j = 1; j < sortedTable.length; j++) {
@@ -72,7 +73,7 @@ public static String encrpyter(String word, String keyword, String abecedarium)
* @return a String decrypted with the word encrypted by the Columnar
* Transposition Cipher Rule
*/
- public static String decrypter() {
+ public static String decrypt() {
StringBuilder wordDecrypted = new StringBuilder();
for (int i = 1; i < table.length; i++) {
for (Object item : table[i]) {
@@ -91,14 +92,14 @@ public static String decrypter() {
*/
private static Object[][] tableBuilder(String word) {
Object[][] table = new Object[numberOfRows(word) + 1][keyword.length()];
- char[] wordInChards = word.toCharArray();
- // Fils in the respective numbers
+ char[] wordInChars = word.toCharArray();
+ // Fills in the respective numbers for the column
table[0] = findElements();
int charElement = 0;
for (int i = 1; i < table.length; i++) {
for (int j = 0; j < table[i].length; j++) {
- if (charElement < wordInChards.length) {
- table[i][j] = wordInChards[charElement];
+ if (charElement < wordInChars.length) {
+ table[i][j] = wordInChars[charElement];
charElement++;
} else {
table[i][j] = ENCRYPTION_FIELD_CHAR;
@@ -116,7 +117,7 @@ private static Object[][] tableBuilder(String word) {
* order to respect the Columnar Transposition Cipher Rule.
*/
private static int numberOfRows(String word) {
- if (word.length() / keyword.length() > word.length() / keyword.length()) {
+ if (word.length() % keyword.length() != 0) {
return (word.length() / keyword.length()) + 1;
} else {
return word.length() / keyword.length();
@@ -173,35 +174,13 @@ private static void switchColumns(Object[][] table, int firstColumnIndex, int se
}
/**
- * Creates an abecedarium with a specified ascii inded
- *
- * @param value Number of characters being used based on the ASCII Table
+ * Creates an abecedarium with all available ascii values.
*/
- private static void abecedariumBuilder(int value) {
+ private static void abecedariumBuilder() {
StringBuilder t = new StringBuilder();
- for (int i = 0; i < value; i++) {
+ for (int i = 0; i < 256; i++) {
t.append((char) i);
}
abecedarium = t.toString();
}
-
- private static void showTable() {
- for (Object[] table1 : table) {
- for (Object item : table1) {
- System.out.print(item + " ");
- }
- System.out.println();
- }
- }
-
- public static void main(String[] args) {
- String keywordForExample = "asd215";
- String wordBeingEncrypted = "This is a test of the Columnar Transposition Cipher";
- System.out.println("### Example of Columnar Transposition Cipher ###\n");
- System.out.println("Word being encryped ->>> " + wordBeingEncrypted);
- System.out.println("Word encrypted ->>> " + ColumnarTranspositionCipher.encrpyter(wordBeingEncrypted, keywordForExample));
- System.out.println("Word decryped ->>> " + ColumnarTranspositionCipher.decrypter());
- System.out.println("\n### Encrypted Table ###");
- showTable();
- }
}
diff --git a/src/main/java/com/thealgorithms/ciphers/DiffieHellman.java b/src/main/java/com/thealgorithms/ciphers/DiffieHellman.java
new file mode 100644
index 000000000000..7470b40e001a
--- /dev/null
+++ b/src/main/java/com/thealgorithms/ciphers/DiffieHellman.java
@@ -0,0 +1,36 @@
+package com.thealgorithms.ciphers;
+
+import java.math.BigInteger;
+
+public final class DiffieHellman {
+
+ private final BigInteger base;
+ private final BigInteger secret;
+ private final BigInteger prime;
+
+ // Constructor to initialize base, secret, and prime
+ public DiffieHellman(BigInteger base, BigInteger secret, BigInteger prime) {
+ // Check for non-null and positive values
+ if (base == null || secret == null || prime == null || base.signum() <= 0 || secret.signum() <= 0 || prime.signum() <= 0) {
+ throw new IllegalArgumentException("Base, secret, and prime must be non-null and positive values.");
+ }
+ this.base = base;
+ this.secret = secret;
+ this.prime = prime;
+ }
+
+ // Method to calculate public value (g^x mod p)
+ public BigInteger calculatePublicValue() {
+ // Returns g^x mod p
+ return base.modPow(secret, prime);
+ }
+
+ // Method to calculate the shared secret key (otherPublic^secret mod p)
+ public BigInteger calculateSharedSecret(BigInteger otherPublicValue) {
+ if (otherPublicValue == null || otherPublicValue.signum() <= 0) {
+ throw new IllegalArgumentException("Other public value must be non-null and positive.");
+ }
+ // Returns b^x mod p or a^y mod p
+ return otherPublicValue.modPow(secret, prime);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/ciphers/ECC.java b/src/main/java/com/thealgorithms/ciphers/ECC.java
new file mode 100644
index 000000000000..7b1e37f0e1e1
--- /dev/null
+++ b/src/main/java/com/thealgorithms/ciphers/ECC.java
@@ -0,0 +1,236 @@
+package com.thealgorithms.ciphers;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+/**
+ * ECC - Elliptic Curve Cryptography
+ * Elliptic Curve Cryptography is a public-key cryptography method that uses the algebraic structure of
+ * elliptic curves over finite fields. ECC provides a higher level of security with smaller key sizes compared
+ * to other public-key methods like RSA, making it particularly suitable for environments where computational
+ * resources are limited, such as mobile devices and embedded systems.
+ *
+ * This class implements elliptic curve cryptography, providing encryption and decryption
+ * functionalities based on public and private key pairs.
+ *
+ * @author xuyang
+ */
+public class ECC {
+
+ private BigInteger privateKey; // Private key used for decryption
+ private ECPoint publicKey; // Public key used for encryption
+ private EllipticCurve curve; // Elliptic curve used in cryptography
+ private ECPoint basePoint; // Base point G on the elliptic curve
+
+ public ECC(int bits) {
+ generateKeys(bits); // Generates public-private key pair
+ }
+
+ public EllipticCurve getCurve() {
+ return curve; // Returns the elliptic curve
+ }
+
+ public void setCurve(EllipticCurve curve) {
+ this.curve = curve;
+ }
+
+ // Getter and Setter for private key
+ public BigInteger getPrivateKey() {
+ return privateKey;
+ }
+
+ public void setPrivateKey(BigInteger privateKey) {
+ this.privateKey = privateKey;
+ }
+
+ /**
+ * Encrypts the message using the public key.
+ * The message is transformed into an ECPoint and encrypted with elliptic curve operations.
+ *
+ * @param message The plain message to be encrypted
+ * @return The encrypted message as an array of ECPoints (R, S)
+ */
+ public ECPoint[] encrypt(String message) {
+ BigInteger m = new BigInteger(message.getBytes()); // Convert message to BigInteger
+ SecureRandom r = new SecureRandom(); // Generate random value for k
+ BigInteger k = new BigInteger(curve.getFieldSize(), r); // Generate random scalar k
+
+ // Calculate point r = k * G, where G is the base point
+ ECPoint rPoint = basePoint.multiply(k, curve.getP(), curve.getA());
+
+ // Calculate point s = k * publicKey + encodedMessage
+ ECPoint sPoint = publicKey.multiply(k, curve.getP(), curve.getA()).add(curve.encodeMessage(m), curve.getP(), curve.getA());
+
+ return new ECPoint[] {rPoint, sPoint}; // Return encrypted message as two ECPoints
+ }
+
+ /**
+ * Decrypts the encrypted message using the private key.
+ * The decryption process is the reverse of encryption, recovering the original message.
+ *
+ * @param encryptedMessage The encrypted message as an array of ECPoints (R, S)
+ * @return The decrypted plain message as a String
+ */
+ public String decrypt(ECPoint[] encryptedMessage) {
+ ECPoint rPoint = encryptedMessage[0]; // First part of ciphertext
+ ECPoint sPoint = encryptedMessage[1]; // Second part of ciphertext
+
+ // Perform decryption: s - r * privateKey
+ ECPoint decodedMessage = sPoint.subtract(rPoint.multiply(privateKey, curve.getP(), curve.getA()), curve.getP(), curve.getA());
+
+ BigInteger m = curve.decodeMessage(decodedMessage); // Decode the message from ECPoint
+
+ return new String(m.toByteArray()); // Convert BigInteger back to String
+ }
+
+ /**
+ * Generates a new public-private key pair for encryption and decryption.
+ *
+ * @param bits The size (in bits) of the keys to generate
+ */
+ public final void generateKeys(int bits) {
+ SecureRandom r = new SecureRandom();
+ curve = new EllipticCurve(bits); // Initialize a new elliptic curve
+ basePoint = curve.getBasePoint(); // Set the base point G
+
+ // Generate private key as a random BigInteger
+ privateKey = new BigInteger(bits, r);
+
+ // Generate public key as the point publicKey = privateKey * G
+ publicKey = basePoint.multiply(privateKey, curve.getP(), curve.getA());
+ }
+
+ /**
+ * Class representing an elliptic curve with the form y^2 = x^3 + ax + b.
+ */
+ public static class EllipticCurve {
+ private final BigInteger a; // Coefficient a in the curve equation
+ private final BigInteger b; // Coefficient b in the curve equation
+ private final BigInteger p; // Prime number p, defining the finite field
+ private final ECPoint basePoint; // Base point G on the curve
+
+ // Constructor with explicit parameters for a, b, p, and base point
+ public EllipticCurve(BigInteger a, BigInteger b, BigInteger p, ECPoint basePoint) {
+ this.a = a;
+ this.b = b;
+ this.p = p;
+ this.basePoint = basePoint;
+ }
+
+ // Constructor that randomly generates the curve parameters
+ public EllipticCurve(int bits) {
+ SecureRandom r = new SecureRandom();
+ this.p = BigInteger.probablePrime(bits, r); // Random prime p
+ this.a = new BigInteger(bits, r); // Random coefficient a
+ this.b = new BigInteger(bits, r); // Random coefficient b
+ this.basePoint = new ECPoint(BigInteger.valueOf(4), BigInteger.valueOf(8)); // Fixed base point G
+ }
+
+ public ECPoint getBasePoint() {
+ return basePoint;
+ }
+
+ public BigInteger getP() {
+ return p;
+ }
+
+ public BigInteger getA() {
+ return a;
+ }
+
+ public BigInteger getB() {
+ return b;
+ }
+
+ public int getFieldSize() {
+ return p.bitLength();
+ }
+
+ public ECPoint encodeMessage(BigInteger message) {
+ // Simple encoding of a message as an ECPoint (this is a simplified example)
+ return new ECPoint(message, message);
+ }
+
+ public BigInteger decodeMessage(ECPoint point) {
+ return point.getX(); // Decode the message from ECPoint (simplified)
+ }
+ }
+
+ /**
+ * Class representing a point on the elliptic curve.
+ */
+ public static class ECPoint {
+ private final BigInteger x; // X-coordinate of the point
+ private final BigInteger y; // Y-coordinate of the point
+
+ public ECPoint(BigInteger x, BigInteger y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public BigInteger getX() {
+ return x;
+ }
+
+ public BigInteger getY() {
+ return y;
+ }
+
+ @Override
+ public String toString() {
+ return "ECPoint(x=" + x.toString() + ", y=" + y.toString() + ")";
+ }
+
+ /**
+ * Add two points on the elliptic curve.
+ */
+ public ECPoint add(ECPoint other, BigInteger p, BigInteger a) {
+ if (this.x.equals(BigInteger.ZERO) && this.y.equals(BigInteger.ZERO)) {
+ return other; // If this point is the identity, return the other point
+ }
+ if (other.x.equals(BigInteger.ZERO) && other.y.equals(BigInteger.ZERO)) {
+ return this; // If the other point is the identity, return this point
+ }
+
+ BigInteger lambda;
+ if (this.equals(other)) {
+ // Special case: point doubling
+ lambda = this.x.pow(2).multiply(BigInteger.valueOf(3)).add(a).multiply(this.y.multiply(BigInteger.valueOf(2)).modInverse(p)).mod(p);
+ } else {
+ // General case: adding two different points
+ lambda = other.y.subtract(this.y).multiply(other.x.subtract(this.x).modInverse(p)).mod(p);
+ }
+
+ BigInteger xr = lambda.pow(2).subtract(this.x).subtract(other.x).mod(p);
+ BigInteger yr = lambda.multiply(this.x.subtract(xr)).subtract(this.y).mod(p);
+
+ return new ECPoint(xr, yr);
+ }
+
+ /**
+ * Subtract two points on the elliptic curve.
+ */
+ public ECPoint subtract(ECPoint other, BigInteger p, BigInteger a) {
+ ECPoint negOther = new ECPoint(other.x, p.subtract(other.y)); // Negate the Y coordinate
+ return this.add(negOther, p, a); // Add the negated point
+ }
+
+ /**
+ * Multiply a point by a scalar (repeated addition).
+ */
+ public ECPoint multiply(BigInteger k, BigInteger p, BigInteger a) {
+ ECPoint result = new ECPoint(BigInteger.ZERO, BigInteger.ZERO); // Identity point
+ ECPoint addend = this;
+
+ while (k.signum() > 0) {
+ if (k.testBit(0)) {
+ result = result.add(addend, p, a); // Add the current point
+ }
+ addend = addend.add(addend, p, a); // Double the point
+ k = k.shiftRight(1); // Divide k by 2
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/ciphers/HillCipher.java b/src/main/java/com/thealgorithms/ciphers/HillCipher.java
index 780009c2f1d6..01b1aeb8bc6c 100644
--- a/src/main/java/com/thealgorithms/ciphers/HillCipher.java
+++ b/src/main/java/com/thealgorithms/ciphers/HillCipher.java
@@ -1,178 +1,103 @@
package com.thealgorithms.ciphers;
-import java.util.Scanner;
+public class HillCipher {
-/*
- * Java Implementation of Hill Cipher
- * Hill cipher is a polyalphabetic substitution cipher. Each letter is represented by a number
- * belonging to the set Z26 where A=0 , B=1, ..... Z=25. To encrypt a message, each block of n
- * letters (since matrix size is n x n) is multiplied by an invertible n × n matrix, against
- * modulus 26. To decrypt the message, each block is multiplied by the inverse of the matrix used
- * for encryption. The cipher key and plaintext/ciphertext are user inputs.
- * @author Ojasva Jain
- */
-public final class HillCipher {
- private HillCipher() {
- }
-
- static Scanner userInput = new Scanner(System.in);
-
- /* Following function encrypts the message
- */
- static void encrypt(String message) {
- message = message.toUpperCase();
- // Get key matrix
- System.out.println("Enter key matrix size");
- int matrixSize = userInput.nextInt();
- System.out.println("Enter Key/encryptionKey matrix ");
- int[][] keyMatrix = new int[matrixSize][matrixSize];
- for (int i = 0; i < matrixSize; i++) {
- for (int j = 0; j < matrixSize; j++) {
- keyMatrix[i][j] = userInput.nextInt();
- }
- }
- // check if det = 0
+ // Encrypts the message using the key matrix
+ public String encrypt(String message, int[][] keyMatrix) {
+ message = message.toUpperCase().replaceAll("[^A-Z]", "");
+ int matrixSize = keyMatrix.length;
validateDeterminant(keyMatrix, matrixSize);
- int[][] messageVector = new int[matrixSize][1];
- String cipherText = "";
- int[][] cipherMatrix = new int[matrixSize][1];
- int j = 0;
- while (j < message.length()) {
+ StringBuilder cipherText = new StringBuilder();
+ int[] messageVector = new int[matrixSize];
+ int[] cipherVector = new int[matrixSize];
+ int index = 0;
+
+ while (index < message.length()) {
for (int i = 0; i < matrixSize; i++) {
- if (j >= message.length()) {
- messageVector[i][0] = 23;
+ if (index < message.length()) {
+ messageVector[i] = message.charAt(index++) - 'A';
} else {
- messageVector[i][0] = (message.charAt(j)) % 65;
+ messageVector[i] = 'X' - 'A'; // Padding with 'X' if needed
}
- System.out.println(messageVector[i][0]);
- j++;
}
- int x;
- int i;
- for (i = 0; i < matrixSize; i++) {
- cipherMatrix[i][0] = 0;
- for (x = 0; x < matrixSize; x++) {
- cipherMatrix[i][0] += keyMatrix[i][x] * messageVector[x][0];
+ for (int i = 0; i < matrixSize; i++) {
+ cipherVector[i] = 0;
+ for (int j = 0; j < matrixSize; j++) {
+ cipherVector[i] += keyMatrix[i][j] * messageVector[j];
}
- System.out.println(cipherMatrix[i][0]);
- cipherMatrix[i][0] = cipherMatrix[i][0] % 26;
- }
- for (i = 0; i < matrixSize; i++) {
- cipherText += (char) (cipherMatrix[i][0] + 65);
+ cipherVector[i] = cipherVector[i] % 26;
+ cipherText.append((char) (cipherVector[i] + 'A'));
}
}
- System.out.println("Ciphertext: " + cipherText);
+
+ return cipherText.toString();
}
- // Following function decrypts a message
- static void decrypt(String message) {
- message = message.toUpperCase();
- // Get key matrix
- System.out.println("Enter key matrix size");
- int n = userInput.nextInt();
- System.out.println("Enter inverseKey/decryptionKey matrix ");
- int[][] keyMatrix = new int[n][n];
- for (int i = 0; i < n; i++) {
- for (int j = 0; j < n; j++) {
- keyMatrix[i][j] = userInput.nextInt();
- }
- }
- // check if det = 0
- validateDeterminant(keyMatrix, n);
+ // Decrypts the message using the inverse key matrix
+ public String decrypt(String message, int[][] inverseKeyMatrix) {
+ message = message.toUpperCase().replaceAll("[^A-Z]", "");
+ int matrixSize = inverseKeyMatrix.length;
+ validateDeterminant(inverseKeyMatrix, matrixSize);
+
+ StringBuilder plainText = new StringBuilder();
+ int[] messageVector = new int[matrixSize];
+ int[] plainVector = new int[matrixSize];
+ int index = 0;
- // solving for the required plaintext message
- int[][] messageVector = new int[n][1];
- String plainText = "";
- int[][] plainMatrix = new int[n][1];
- int j = 0;
- while (j < message.length()) {
- for (int i = 0; i < n; i++) {
- if (j >= message.length()) {
- messageVector[i][0] = 23;
+ while (index < message.length()) {
+ for (int i = 0; i < matrixSize; i++) {
+ if (index < message.length()) {
+ messageVector[i] = message.charAt(index++) - 'A';
} else {
- messageVector[i][0] = (message.charAt(j)) % 65;
+ messageVector[i] = 'X' - 'A'; // Padding with 'X' if needed
}
- System.out.println(messageVector[i][0]);
- j++;
}
- int x;
- int i;
- for (i = 0; i < n; i++) {
- plainMatrix[i][0] = 0;
- for (x = 0; x < n; x++) {
- plainMatrix[i][0] += keyMatrix[i][x] * messageVector[x][0];
+ for (int i = 0; i < matrixSize; i++) {
+ plainVector[i] = 0;
+ for (int j = 0; j < matrixSize; j++) {
+ plainVector[i] += inverseKeyMatrix[i][j] * messageVector[j];
}
-
- plainMatrix[i][0] = plainMatrix[i][0] % 26;
- }
- for (i = 0; i < n; i++) {
- plainText += (char) (plainMatrix[i][0] + 65);
+ plainVector[i] = plainVector[i] % 26;
+ plainText.append((char) (plainVector[i] + 'A'));
}
}
- System.out.println("Plaintext: " + plainText);
+
+ return plainText.toString();
}
- // Determinant calculator
- public static int determinant(int[][] a, int n) {
- int det = 0;
- int sign = 1;
- int p = 0;
- int q = 0;
+ // Validates that the determinant of the key matrix is not zero modulo 26
+ private void validateDeterminant(int[][] keyMatrix, int n) {
+ int det = determinant(keyMatrix, n) % 26;
+ if (det == 0) {
+ throw new IllegalArgumentException("Invalid key matrix. Determinant is zero modulo 26.");
+ }
+ }
+ // Computes the determinant of a matrix recursively
+ private int determinant(int[][] matrix, int n) {
+ int det = 0;
if (n == 1) {
- det = a[0][0];
- } else {
- int[][] b = new int[n - 1][n - 1];
- for (int x = 0; x < n; x++) {
- p = 0;
- q = 0;
- for (int i = 1; i < n; i++) {
- for (int j = 0; j < n; j++) {
- if (j != x) {
- b[p][q++] = a[i][j];
- if (q % (n - 1) == 0) {
- p++;
- q = 0;
- }
- }
+ return matrix[0][0];
+ }
+ int sign = 1;
+ int[][] subMatrix = new int[n - 1][n - 1];
+ for (int x = 0; x < n; x++) {
+ int subI = 0;
+ for (int i = 1; i < n; i++) {
+ int subJ = 0;
+ for (int j = 0; j < n; j++) {
+ if (j != x) {
+ subMatrix[subI][subJ++] = matrix[i][j];
}
}
- det = det + a[0][x] * determinant(b, n - 1) * sign;
- sign = -sign;
+ subI++;
}
+ det += sign * matrix[0][x] * determinant(subMatrix, n - 1);
+ sign = -sign;
}
return det;
}
-
- // Function to implement Hill Cipher
- static void hillCipher(String message) {
- System.out.println("What do you want to process from the message?");
- System.out.println("Press 1: To Encrypt");
- System.out.println("Press 2: To Decrypt");
- short sc = userInput.nextShort();
- if (sc == 1) {
- encrypt(message);
- } else if (sc == 2) {
- decrypt(message);
- } else {
- System.out.println("Invalid input, program terminated.");
- }
- }
-
- static void validateDeterminant(int[][] keyMatrix, int n) {
- if (determinant(keyMatrix, n) % 26 == 0) {
- System.out.println("Invalid key, as determinant = 0. Program Terminated");
- }
- }
-
- // Driver code
- public static void main(String[] args) {
- // Get the message to be encrypted
- System.out.println("Enter message");
- String message = userInput.nextLine();
- hillCipher(message);
- }
}
diff --git a/src/main/java/com/thealgorithms/ciphers/MonoAlphabetic.java b/src/main/java/com/thealgorithms/ciphers/MonoAlphabetic.java
new file mode 100644
index 000000000000..1d5b7110a6f3
--- /dev/null
+++ b/src/main/java/com/thealgorithms/ciphers/MonoAlphabetic.java
@@ -0,0 +1,48 @@
+package com.thealgorithms.ciphers;
+
+public final class MonoAlphabetic {
+
+ private MonoAlphabetic() {
+ throw new UnsupportedOperationException("Utility class");
+ }
+
+ // Encryption method
+ public static String encrypt(String data, String key) {
+ if (!data.matches("[A-Z]+")) {
+ throw new IllegalArgumentException("Input data contains invalid characters. Only uppercase A-Z are allowed.");
+ }
+ StringBuilder sb = new StringBuilder();
+
+ // Encrypt each character
+ for (char c : data.toCharArray()) {
+ int idx = charToPos(c); // Get the index of the character
+ sb.append(key.charAt(idx)); // Map to the corresponding character in the key
+ }
+ return sb.toString();
+ }
+
+ // Decryption method
+ public static String decrypt(String data, String key) {
+ StringBuilder sb = new StringBuilder();
+
+ // Decrypt each character
+ for (char c : data.toCharArray()) {
+ int idx = key.indexOf(c); // Find the index of the character in the key
+ if (idx == -1) {
+ throw new IllegalArgumentException("Input data contains invalid characters.");
+ }
+ sb.append(posToChar(idx)); // Convert the index back to the original character
+ }
+ return sb.toString();
+ }
+
+ // Helper method: Convert a character to its position in the alphabet
+ private static int charToPos(char c) {
+ return c - 'A'; // Subtract 'A' to get position (0 for A, 1 for B, etc.)
+ }
+
+ // Helper method: Convert a position in the alphabet to a character
+ private static char posToChar(int pos) {
+ return (char) (pos + 'A'); // Add 'A' to convert position back to character
+ }
+}
diff --git a/src/main/java/com/thealgorithms/ciphers/RSA.java b/src/main/java/com/thealgorithms/ciphers/RSA.java
index f50e501e68c8..28af1a62032a 100644
--- a/src/main/java/com/thealgorithms/ciphers/RSA.java
+++ b/src/main/java/com/thealgorithms/ciphers/RSA.java
@@ -4,7 +4,27 @@
import java.security.SecureRandom;
/**
- * @author Nguyen Duy Tiep on 23-Oct-17.
+ * RSA is an asymmetric cryptographic algorithm used for secure data encryption and decryption.
+ * It relies on a pair of keys: a public key (used for encryption) and a private key
+ * (used for decryption). The algorithm is based on the difficulty of factoring large prime numbers.
+ *
+ * This implementation includes key generation, encryption, and decryption methods that can handle both
+ * text-based messages and BigInteger inputs. For more details on RSA:
+ * RSA Cryptosystem - Wikipedia.
+ *
+ * Example Usage:
+ *
+ * RSA rsa = new RSA(1024);
+ * String encryptedMessage = rsa.encrypt("Hello RSA!");
+ * String decryptedMessage = rsa.decrypt(encryptedMessage);
+ * System.out.println(decryptedMessage); // Output: Hello RSA!
+ *
+ *
+ * Note: The key size directly affects the security and performance of the RSA algorithm.
+ * Larger keys are more secure but slower to compute.
+ *
+ * @author Nguyen Duy Tiep
+ * @version 23-Oct-17
*/
public class RSA {
@@ -12,55 +32,88 @@ public class RSA {
private BigInteger privateKey;
private BigInteger publicKey;
+ /**
+ * Constructor that generates RSA keys with the specified number of bits.
+ *
+ * @param bits The bit length of the keys to be generated. Common sizes include 512, 1024, 2048, etc.
+ */
public RSA(int bits) {
generateKeys(bits);
}
/**
- * @return encrypted message
+ * Encrypts a text message using the RSA public key.
+ *
+ * @param message The plaintext message to be encrypted.
+ * @throws IllegalArgumentException If the message is empty.
+ * @return The encrypted message represented as a String.
*/
public synchronized String encrypt(String message) {
+ if (message.isEmpty()) {
+ throw new IllegalArgumentException("Message is empty");
+ }
return (new BigInteger(message.getBytes())).modPow(publicKey, modulus).toString();
}
/**
- * @return encrypted message as big integer
+ * Encrypts a BigInteger message using the RSA public key.
+ *
+ * @param message The plaintext message as a BigInteger.
+ * @return The encrypted message as a BigInteger.
*/
public synchronized BigInteger encrypt(BigInteger message) {
return message.modPow(publicKey, modulus);
}
/**
- * @return plain message
+ * Decrypts an encrypted message (as String) using the RSA private key.
+ *
+ * @param encryptedMessage The encrypted message to be decrypted, represented as a String.
+ * @throws IllegalArgumentException If the message is empty.
+ * @return The decrypted plaintext message as a String.
*/
public synchronized String decrypt(String encryptedMessage) {
+ if (encryptedMessage.isEmpty()) {
+ throw new IllegalArgumentException("Message is empty");
+ }
return new String((new BigInteger(encryptedMessage)).modPow(privateKey, modulus).toByteArray());
}
/**
- * @return plain message as big integer
+ * Decrypts an encrypted BigInteger message using the RSA private key.
+ *
+ * @param encryptedMessage The encrypted message as a BigInteger.
+ * @return The decrypted plaintext message as a BigInteger.
*/
public synchronized BigInteger decrypt(BigInteger encryptedMessage) {
return encryptedMessage.modPow(privateKey, modulus);
}
/**
- * Generate a new public and private key set.
+ * Generates a new RSA key pair (public and private keys) with the specified bit length.
+ * Steps:
+ * 1. Generate two large prime numbers p and q.
+ * 2. Compute the modulus n = p * q.
+ * 3. Compute Euler's totient function: φ(n) = (p-1) * (q-1).
+ * 4. Choose a public key e (starting from 3) that is coprime with φ(n).
+ * 5. Compute the private key d as the modular inverse of e mod φ(n).
+ * The public key is (e, n) and the private key is (d, n).
+ *
+ * @param bits The bit length of the keys to be generated.
*/
public final synchronized void generateKeys(int bits) {
- SecureRandom r = new SecureRandom();
- BigInteger p = new BigInteger(bits / 2, 100, r);
- BigInteger q = new BigInteger(bits / 2, 100, r);
+ SecureRandom random = new SecureRandom();
+ BigInteger p = new BigInteger(bits / 2, 100, random);
+ BigInteger q = new BigInteger(bits / 2, 100, random);
modulus = p.multiply(q);
- BigInteger m = (p.subtract(BigInteger.ONE)).multiply(q.subtract(BigInteger.ONE));
+ BigInteger phi = (p.subtract(BigInteger.ONE)).multiply(q.subtract(BigInteger.ONE));
publicKey = BigInteger.valueOf(3L);
-
- while (m.gcd(publicKey).intValue() > 1) {
+ while (phi.gcd(publicKey).intValue() > 1) {
publicKey = publicKey.add(BigInteger.TWO);
}
- privateKey = publicKey.modInverse(m);
+ privateKey = publicKey.modInverse(phi);
}
}
diff --git a/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java b/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java
new file mode 100644
index 000000000000..f81252980468
--- /dev/null
+++ b/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java
@@ -0,0 +1,147 @@
+package com.thealgorithms.ciphers;
+
+import java.util.Arrays;
+
+/**
+ * The rail fence cipher (also called a zigzag cipher) is a classical type of transposition cipher.
+ * It derives its name from the manner in which encryption is performed, in analogy to a fence built with horizontal rails.
+ * https://en.wikipedia.org/wiki/Rail_fence_cipher
+ * @author https://github.com/Krounosity
+ */
+
+public class RailFenceCipher {
+
+ // Encrypts the input string using the rail fence cipher method with the given number of rails.
+ public String encrypt(String str, int rails) {
+
+ // Base case of single rail or rails are more than the number of characters in the string
+ if (rails == 1 || rails >= str.length()) {
+ return str;
+ }
+
+ // Boolean flag to determine if the movement is downward or upward in the rail matrix.
+ boolean down = true;
+ // Create a 2D array to represent the rails (rows) and the length of the string (columns).
+ char[][] strRail = new char[rails][str.length()];
+
+ // Initialize all positions in the rail matrix with a placeholder character ('\n').
+ for (int i = 0; i < rails; i++) {
+ Arrays.fill(strRail[i], '\n');
+ }
+
+ int row = 0; // Start at the first row
+ int col = 0; // Start at the first column
+
+ int i = 0;
+
+ // Fill the rail matrix with characters from the string based on the rail pattern.
+ while (col < str.length()) {
+ // Change direction to down when at the first row.
+ if (row == 0) {
+ down = true;
+ }
+ // Change direction to up when at the last row.
+ else if (row == rails - 1) {
+ down = false;
+ }
+
+ // Place the character in the current position of the rail matrix.
+ strRail[row][col] = str.charAt(i);
+ col++; // Move to the next column.
+ // Move to the next row based on the direction.
+ if (down) {
+ row++;
+ } else {
+ row--;
+ }
+
+ i++;
+ }
+
+ // Construct the encrypted string by reading characters row by row.
+ StringBuilder encryptedString = new StringBuilder();
+ for (char[] chRow : strRail) {
+ for (char ch : chRow) {
+ if (ch != '\n') {
+ encryptedString.append(ch);
+ }
+ }
+ }
+ return encryptedString.toString();
+ }
+ // Decrypts the input string using the rail fence cipher method with the given number of rails.
+ public String decrypt(String str, int rails) {
+
+ // Base case of single rail or rails are more than the number of characters in the string
+ if (rails == 1 || rails >= str.length()) {
+ return str;
+ }
+ // Boolean flag to determine if the movement is downward or upward in the rail matrix.
+ boolean down = true;
+
+ // Create a 2D array to represent the rails (rows) and the length of the string (columns).
+ char[][] strRail = new char[rails][str.length()];
+
+ int row = 0; // Start at the first row
+ int col = 0; // Start at the first column
+
+ // Mark the pattern on the rail matrix using '*'.
+ while (col < str.length()) {
+ // Change direction to down when at the first row.
+ if (row == 0) {
+ down = true;
+ }
+ // Change direction to up when at the last row.
+ else if (row == rails - 1) {
+ down = false;
+ }
+
+ // Mark the current position in the rail matrix.
+ strRail[row][col] = '*';
+ col++; // Move to the next column.
+ // Move to the next row based on the direction.
+ if (down) {
+ row++;
+ } else {
+ row--;
+ }
+ }
+
+ int index = 0; // Index to track characters from the input string.
+ // Fill the rail matrix with characters from the input string based on the marked pattern.
+ for (int i = 0; i < rails; i++) {
+ for (int j = 0; j < str.length(); j++) {
+ if (strRail[i][j] == '*') {
+ strRail[i][j] = str.charAt(index++);
+ }
+ }
+ }
+
+ // Construct the decrypted string by following the zigzag pattern.
+ StringBuilder decryptedString = new StringBuilder();
+ row = 0; // Reset to the first row
+ col = 0; // Reset to the first column
+
+ while (col < str.length()) {
+ // Change direction to down when at the first row.
+ if (row == 0) {
+ down = true;
+ }
+ // Change direction to up when at the last row.
+ else if (row == rails - 1) {
+ down = false;
+ }
+ // Append the character from the rail matrix to the decrypted string.
+ decryptedString.append(strRail[row][col]);
+ col++; // Move to the next column.
+ // Move to the next row based on the direction.
+ if (down) {
+ row++;
+ } else {
+ row--;
+ }
+ }
+
+ return decryptedString.toString();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/ciphers/Vigenere.java b/src/main/java/com/thealgorithms/ciphers/Vigenere.java
index 1702f1abb94c..0f117853bb85 100644
--- a/src/main/java/com/thealgorithms/ciphers/Vigenere.java
+++ b/src/main/java/com/thealgorithms/ciphers/Vigenere.java
@@ -1,16 +1,54 @@
package com.thealgorithms.ciphers;
/**
- * A Java implementation of Vigenere Cipher.
+ * A Java implementation of the Vigenère Cipher.
+ *
+ * The Vigenère Cipher is a polyalphabetic substitution cipher that uses a
+ * keyword to shift letters in the plaintext by different amounts, depending
+ * on the corresponding character in the keyword. It wraps around the alphabet,
+ * ensuring the shifts are within 'A'-'Z' or 'a'-'z'.
+ *
+ * Non-alphabetic characters (like spaces, punctuation) are kept unchanged.
+ *
+ * Encryption Example:
+ * - Plaintext: "Hello World!"
+ * - Key: "suchsecret"
+ * - Encrypted Text: "Zynsg Yfvev!"
+ *
+ * Decryption Example:
+ * - Ciphertext: "Zynsg Yfvev!"
+ * - Key: "suchsecret"
+ * - Decrypted Text: "Hello World!"
+ *
+ * Wikipedia Reference:
+ * Vigenère Cipher - Wikipedia
*
* @author straiffix
* @author beingmartinbmc
*/
public class Vigenere {
+ /**
+ * Encrypts a given message using the Vigenère Cipher with the specified key.
+ * Steps:
+ * 1. Iterate over each character in the message.
+ * 2. If the character is a letter, shift it by the corresponding character in the key.
+ * 3. Preserve the case of the letter.
+ * 4. Preserve non-alphabetic characters.
+ * 5. Move to the next character in the key (cyclic).
+ * 6. Return the encrypted message.
+ *
+ * @param message The plaintext message to encrypt.
+ * @param key The keyword used for encryption.
+ * @throws IllegalArgumentException if the key is empty.
+ * @return The encrypted message.
+ */
public String encrypt(final String message, final String key) {
- StringBuilder result = new StringBuilder();
+ if (key.isEmpty()) {
+ throw new IllegalArgumentException("Key cannot be empty.");
+ }
+ StringBuilder result = new StringBuilder();
int j = 0;
for (int i = 0; i < message.length(); i++) {
char c = message.charAt(i);
@@ -20,17 +58,35 @@ public String encrypt(final String message, final String key) {
} else {
result.append((char) ((c + key.toLowerCase().charAt(j) - 2 * 'a') % 26 + 'a'));
}
+ j = ++j % key.length();
} else {
result.append(c);
}
- j = ++j % key.length();
}
return result.toString();
}
+ /**
+ * Decrypts a given message encrypted with the Vigenère Cipher using the specified key.
+ * Steps:
+ * 1. Iterate over each character in the message.
+ * 2. If the character is a letter, shift it back by the corresponding character in the key.
+ * 3. Preserve the case of the letter.
+ * 4. Preserve non-alphabetic characters.
+ * 5. Move to the next character in the key (cyclic).
+ * 6. Return the decrypted message.
+ *
+ * @param message The encrypted message to decrypt.
+ * @param key The keyword used for decryption.
+ * @throws IllegalArgumentException if the key is empty.
+ * @return The decrypted plaintext message.
+ */
public String decrypt(final String message, final String key) {
- StringBuilder result = new StringBuilder();
+ if (key.isEmpty()) {
+ throw new IllegalArgumentException("Key cannot be empty.");
+ }
+ StringBuilder result = new StringBuilder();
int j = 0;
for (int i = 0; i < message.length(); i++) {
char c = message.charAt(i);
@@ -40,11 +96,10 @@ public String decrypt(final String message, final String key) {
} else {
result.append((char) ('z' - (25 - (c - key.toLowerCase().charAt(j))) % 26));
}
+ j = ++j % key.length();
} else {
result.append(c);
}
-
- j = ++j % key.length();
}
return result.toString();
}
diff --git a/src/main/java/com/thealgorithms/ciphers/XORCipher.java b/src/main/java/com/thealgorithms/ciphers/XORCipher.java
new file mode 100644
index 000000000000..a612ccfbcdef
--- /dev/null
+++ b/src/main/java/com/thealgorithms/ciphers/XORCipher.java
@@ -0,0 +1,95 @@
+package com.thealgorithms.ciphers;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.HexFormat;
+
+/**
+ * A simple implementation of the XOR cipher that allows both encryption and decryption
+ * using a given key. This cipher works by applying the XOR bitwise operation between
+ * the bytes of the input text and the corresponding bytes of the key (repeating the key
+ * if necessary).
+ *
+ * Usage:
+ * - Encryption: Converts plaintext into a hexadecimal-encoded ciphertext.
+ * - Decryption: Converts the hexadecimal ciphertext back into plaintext.
+ *
+ * Characteristics:
+ * - Symmetric: The same key is used for both encryption and decryption.
+ * - Simple but vulnerable: XOR encryption is insecure for real-world cryptography,
+ * especially when the same key is reused.
+ *
+ * Example:
+ * Plaintext: "Hello!"
+ * Key: "key"
+ * Encrypted: "27090c03120b"
+ * Decrypted: "Hello!"
+ *
+ * Reference: XOR Cipher - Wikipedia
+ *
+ * @author lcsjunior
+ */
+public final class XORCipher {
+
+ // Default character encoding for string conversion
+ private static final Charset CS_DEFAULT = StandardCharsets.UTF_8;
+
+ private XORCipher() {
+ }
+
+ /**
+ * Applies the XOR operation between the input bytes and the key bytes.
+ * If the key is shorter than the input, it wraps around (cyclically).
+ *
+ * @param inputBytes The input byte array (plaintext or ciphertext).
+ * @param keyBytes The key byte array used for XOR operation.
+ * @return A new byte array containing the XOR result.
+ */
+ public static byte[] xor(final byte[] inputBytes, final byte[] keyBytes) {
+ byte[] outputBytes = new byte[inputBytes.length];
+ for (int i = 0; i < inputBytes.length; ++i) {
+ outputBytes[i] = (byte) (inputBytes[i] ^ keyBytes[i % keyBytes.length]);
+ }
+ return outputBytes;
+ }
+
+ /**
+ * Encrypts the given plaintext using the XOR cipher with the specified key.
+ * The result is a hexadecimal-encoded string representing the ciphertext.
+ *
+ * @param plainText The input plaintext to encrypt.
+ * @param key The encryption key.
+ * @throws IllegalArgumentException if the key is empty.
+ * @return A hexadecimal string representing the encrypted text.
+ */
+ public static String encrypt(final String plainText, final String key) {
+ if (key.isEmpty()) {
+ throw new IllegalArgumentException("Key must not be empty");
+ }
+
+ byte[] plainTextBytes = plainText.getBytes(CS_DEFAULT);
+ byte[] keyBytes = key.getBytes(CS_DEFAULT);
+ byte[] xorResult = xor(plainTextBytes, keyBytes);
+ return HexFormat.of().formatHex(xorResult);
+ }
+
+ /**
+ * Decrypts the given ciphertext (in hexadecimal format) using the XOR cipher
+ * with the specified key. The result is the original plaintext.
+ *
+ * @param cipherText The hexadecimal string representing the encrypted text.
+ * @param key The decryption key (must be the same as the encryption key).
+ * @throws IllegalArgumentException if the key is empty.
+ * @return The decrypted plaintext.
+ */
+ public static String decrypt(final String cipherText, final String key) {
+ if (key.isEmpty()) {
+ throw new IllegalArgumentException("Key must not be empty");
+ }
+
+ byte[] cipherBytes = HexFormat.of().parseHex(cipherText);
+ byte[] keyBytes = key.getBytes(CS_DEFAULT);
+ byte[] xorResult = xor(cipherBytes, keyBytes);
+ return new String(xorResult, CS_DEFAULT);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/ciphers/a5/A5Cipher.java b/src/main/java/com/thealgorithms/ciphers/a5/A5Cipher.java
index b7d36db5c809..cc2e9105229a 100644
--- a/src/main/java/com/thealgorithms/ciphers/a5/A5Cipher.java
+++ b/src/main/java/com/thealgorithms/ciphers/a5/A5Cipher.java
@@ -2,17 +2,43 @@
import java.util.BitSet;
-// https://en.wikipedia.org/wiki/A5/1
+/**
+ * The A5Cipher class implements the A5/1 stream cipher, which is a widely used
+ * encryption algorithm, particularly in mobile communications.
+ *
+ * This implementation uses a key stream generator to produce a stream of bits
+ * that are XORed with the plaintext bits to produce the ciphertext.
+ *
+ *
+ * For more details about the A5/1 algorithm, refer to
+ * Wikipedia.
+ *
+ */
public class A5Cipher {
private final A5KeyStreamGenerator keyStreamGenerator;
- private static final int KEY_STREAM_LENGTH = 228; // 28.5 bytes so we need to pad bytes or something
-
+ private static final int KEY_STREAM_LENGTH = 228; // Length of the key stream in bits (28.5 bytes)
+
+ /**
+ * Constructs an A5Cipher instance with the specified session key and frame counter.
+ *
+ * @param sessionKey a BitSet representing the session key used for encryption.
+ * @param frameCounter a BitSet representing the frame counter that helps in key stream generation.
+ */
public A5Cipher(BitSet sessionKey, BitSet frameCounter) {
keyStreamGenerator = new A5KeyStreamGenerator();
keyStreamGenerator.initialize(sessionKey, frameCounter);
}
+ /**
+ * Encrypts the given plaintext bits using the A5/1 cipher algorithm.
+ *
+ * This method generates a key stream and XORs it with the provided plaintext
+ * bits to produce the ciphertext.
+ *
+ * @param plainTextBits a BitSet representing the plaintext bits to be encrypted.
+ * @return a BitSet containing the encrypted ciphertext bits.
+ */
public BitSet encrypt(BitSet plainTextBits) {
// create a copy
var result = new BitSet(KEY_STREAM_LENGTH);
@@ -24,6 +50,13 @@ public BitSet encrypt(BitSet plainTextBits) {
return result;
}
+ /**
+ * Resets the internal counter of the key stream generator.
+ *
+ * This method can be called to re-initialize the state of the key stream
+ * generator, allowing for new key streams to be generated for subsequent
+ * encryptions.
+ */
public void resetCounter() {
keyStreamGenerator.reInitialize();
}
diff --git a/src/main/java/com/thealgorithms/ciphers/a5/A5KeyStreamGenerator.java b/src/main/java/com/thealgorithms/ciphers/a5/A5KeyStreamGenerator.java
index 0b17a685bc57..ee837ef4241a 100644
--- a/src/main/java/com/thealgorithms/ciphers/a5/A5KeyStreamGenerator.java
+++ b/src/main/java/com/thealgorithms/ciphers/a5/A5KeyStreamGenerator.java
@@ -2,15 +2,39 @@
import java.util.BitSet;
-// TODO: raise exceptions for improper use
+/**
+ * The A5KeyStreamGenerator class is responsible for generating key streams
+ * for the A5/1 encryption algorithm using a combination of Linear Feedback Shift Registers (LFSRs).
+ *
+ *
+ * This class extends the CompositeLFSR and initializes a set of LFSRs with
+ * a session key and a frame counter to produce a pseudo-random key stream.
+ *
+ *
+ *
+ * Note: Proper exception handling for invalid usage is to be implemented.
+ *
+ */
public class A5KeyStreamGenerator extends CompositeLFSR {
private BitSet initialFrameCounter;
private BitSet frameCounter;
private BitSet sessionKey;
private static final int INITIAL_CLOCKING_CYCLES = 100;
- private static final int KEY_STREAM_LENGTH = 228; // 28.5 bytes so we need to pad bytes or something
+ private static final int KEY_STREAM_LENGTH = 228;
+ /**
+ * Initializes the A5KeyStreamGenerator with the specified session key and frame counter.
+ *
+ *
+ * This method sets up the internal state of the LFSRs using the provided
+ * session key and frame counter. It creates three LFSRs with specific
+ * configurations and initializes them.
+ *
+ *
+ * @param sessionKey a BitSet representing the session key used for key stream generation.
+ * @param frameCounter a BitSet representing the frame counter that influences the key stream.
+ */
@Override
public void initialize(BitSet sessionKey, BitSet frameCounter) {
this.sessionKey = sessionKey;
@@ -26,10 +50,26 @@ public void initialize(BitSet sessionKey, BitSet frameCounter) {
registers.forEach(lfsr -> lfsr.initialize(sessionKey, frameCounter));
}
+ /**
+ * Re-initializes the key stream generator with the original session key
+ * and frame counter. This method restores the generator to its initial
+ * state.
+ */
public void reInitialize() {
this.initialize(sessionKey, initialFrameCounter);
}
+ /**
+ * Generates the next key stream of bits.
+ *
+ *
+ * This method performs an initial set of clocking cycles and then retrieves
+ * a key stream of the specified length. After generation, it re-initializes
+ * the internal registers.
+ *
+ *
+ * @return a BitSet containing the generated key stream bits.
+ */
public BitSet getNextKeyStream() {
for (int cycle = 1; cycle <= INITIAL_CLOCKING_CYCLES; ++cycle) {
this.clock();
@@ -45,12 +85,37 @@ public BitSet getNextKeyStream() {
return result;
}
+ /**
+ * Re-initializes the registers for the LFSRs.
+ *
+ *
+ * This method increments the frame counter and re-initializes each LFSR
+ * with the current session key and frame counter.
+ *
+ */
private void reInitializeRegisters() {
incrementFrameCounter();
registers.forEach(lfsr -> lfsr.initialize(sessionKey, frameCounter));
}
+ /**
+ * Increments the current frame counter.
+ *
+ *
+ * This method uses a utility function to increment the frame counter,
+ * which influences the key stream generation process.
+ *
+ */
private void incrementFrameCounter() {
Utils.increment(frameCounter, FRAME_COUNTER_LENGTH);
}
+
+ /**
+ * Retrieves the current frame counter.
+ *
+ * @return a BitSet representing the current state of the frame counter.
+ */
+ public BitSet getFrameCounter() {
+ return frameCounter;
+ }
}
diff --git a/src/main/java/com/thealgorithms/ciphers/a5/CompositeLFSR.java b/src/main/java/com/thealgorithms/ciphers/a5/CompositeLFSR.java
index f96946c39490..029a93848c28 100644
--- a/src/main/java/com/thealgorithms/ciphers/a5/CompositeLFSR.java
+++ b/src/main/java/com/thealgorithms/ciphers/a5/CompositeLFSR.java
@@ -5,13 +5,33 @@
import java.util.Map;
import java.util.TreeMap;
+/**
+ * The CompositeLFSR class represents a composite implementation of
+ * Linear Feedback Shift Registers (LFSRs) for cryptographic purposes.
+ *
+ *
+ * This abstract class manages a collection of LFSR instances and
+ * provides a mechanism for irregular clocking based on the
+ * majority bit among the registers. It implements the BaseLFSR
+ * interface, requiring subclasses to define specific LFSR behaviors.
+ *
+ */
public abstract class CompositeLFSR implements BaseLFSR {
protected final List registers = new ArrayList<>();
/**
- * Implements irregular clocking using the clock bit for each register
- * @return the registers discarded bit xored value
+ * Performs a clocking operation on the composite LFSR.
+ *
+ *
+ * This method determines the majority bit across all registers and
+ * clocks each register based on its clock bit. If a register's
+ * clock bit matches the majority bit, it is clocked (shifted).
+ * The method also computes and returns the XOR of the last bits
+ * of all registers.
+ *
+ *
+ * @return the XOR value of the last bits of all registers.
*/
@Override
public boolean clock() {
@@ -26,6 +46,18 @@ public boolean clock() {
return result;
}
+ /**
+ * Calculates the majority bit among all registers.
+ *
+ *
+ * This private method counts the number of true and false clock bits
+ * across all LFSR registers. It returns true if the count of true
+ * bits is greater than or equal to the count of false bits; otherwise,
+ * it returns false.
+ *
+ *
+ * @return true if the majority clock bits are true; false otherwise.
+ */
private boolean getMajorityBit() {
Map bitCount = new TreeMap<>();
bitCount.put(Boolean.FALSE, 0);
diff --git a/src/main/java/com/thealgorithms/conversions/AffineConverter.java b/src/main/java/com/thealgorithms/conversions/AffineConverter.java
new file mode 100644
index 000000000000..199a6dd517d5
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/AffineConverter.java
@@ -0,0 +1,64 @@
+package com.thealgorithms.conversions;
+
+/**
+ * A utility class to perform affine transformations of the form:
+ * y = slope * x + intercept.
+ *
+ * This class supports inversion and composition of affine transformations.
+ * It is immutable, meaning each instance represents a fixed transformation.
+ */
+public final class AffineConverter {
+ private final double slope;
+ private final double intercept;
+
+ /**
+ * Constructs an AffineConverter with the given slope and intercept.
+ *
+ * @param inSlope The slope of the affine transformation.
+ * @param inIntercept The intercept (constant term) of the affine transformation.
+ * @throws IllegalArgumentException if either parameter is NaN.
+ */
+ public AffineConverter(final double inSlope, final double inIntercept) {
+ if (Double.isNaN(inSlope) || Double.isNaN(inIntercept)) {
+ throw new IllegalArgumentException("Slope and intercept must be valid numbers.");
+ }
+ slope = inSlope;
+ intercept = inIntercept;
+ }
+
+ /**
+ * Converts the given input value using the affine transformation:
+ * result = slope * inValue + intercept.
+ *
+ * @param inValue The input value to convert.
+ * @return The transformed value.
+ */
+ public double convert(final double inValue) {
+ return slope * inValue + intercept;
+ }
+
+ /**
+ * Returns a new AffineConverter representing the inverse of the current transformation.
+ * The inverse of y = slope * x + intercept is x = (y - intercept) / slope.
+ *
+ * @return A new AffineConverter representing the inverse transformation.
+ * @throws AssertionError if the slope is zero, as the inverse would be undefined.
+ */
+ public AffineConverter invert() {
+ assert slope != 0.0 : "Slope cannot be zero for inversion.";
+ return new AffineConverter(1.0 / slope, -intercept / slope);
+ }
+
+ /**
+ * Composes this affine transformation with another, returning a new AffineConverter.
+ * If this transformation is f(x) and the other is g(x), the result is f(g(x)).
+ *
+ * @param other Another AffineConverter to compose with.
+ * @return A new AffineConverter representing the composition of the two transformations.
+ */
+ public AffineConverter compose(final AffineConverter other) {
+ double newSlope = slope * other.slope;
+ double newIntercept = slope * other.intercept + intercept;
+ return new AffineConverter(newSlope, newIntercept);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java b/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java
index 4bd9c74a1751..7a9448fd8fe7 100644
--- a/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java
+++ b/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java
@@ -136,7 +136,7 @@ public static String base2base(String n, int b1, int b2) {
int decimalValue = 0;
int charB2;
char charB1;
- String output = "";
+ StringBuilder output = new StringBuilder();
// Go through every character of n
for (int i = 0; i < n.length(); i++) {
// store the character in charB1
@@ -167,15 +167,15 @@ public static String base2base(String n, int b1, int b2) {
// If the remainder is a digit < 10, simply add it to
// the left side of the new number.
if (decimalValue % b2 < 10) {
- output = decimalValue % b2 + output;
+ output.insert(0, decimalValue % b2);
} // If the remainder is >= 10, add a character with the
// corresponding value to the new number. (A = 10, B = 11, C = 12, ...)
else {
- output = (char) ((decimalValue % b2) + 55) + output;
+ output.insert(0, (char) ((decimalValue % b2) + 55));
}
// Divide by the new base again
decimalValue /= b2;
}
- return output;
+ return output.toString();
}
}
diff --git a/src/main/java/com/thealgorithms/conversions/AnyBaseToDecimal.java b/src/main/java/com/thealgorithms/conversions/AnyBaseToDecimal.java
index 6b4b14adc955..cdab98c7c28a 100644
--- a/src/main/java/com/thealgorithms/conversions/AnyBaseToDecimal.java
+++ b/src/main/java/com/thealgorithms/conversions/AnyBaseToDecimal.java
@@ -3,54 +3,50 @@
/**
* @author Varun Upadhyay (...)
*/
-// Driver program
public final class AnyBaseToDecimal {
- private AnyBaseToDecimal() {
- }
+ private static final int CHAR_OFFSET_FOR_DIGIT = '0';
+ private static final int CHAR_OFFSET_FOR_UPPERCASE = 'A' - 10;
- public static void main(String[] args) {
- assert convertToDecimal("1010", 2) == Integer.valueOf("1010", 2);
- assert convertToDecimal("777", 8) == Integer.valueOf("777", 8);
- assert convertToDecimal("999", 10) == Integer.valueOf("999", 10);
- assert convertToDecimal("ABCDEF", 16) == Integer.valueOf("ABCDEF", 16);
- assert convertToDecimal("XYZ", 36) == Integer.valueOf("XYZ", 36);
+ private AnyBaseToDecimal() {
}
/**
- * Convert any radix to decimal number
+ * Convert any radix to a decimal number.
*
- * @param s the string to be convert
- * @param radix the radix
- * @return decimal of bits
- * @throws NumberFormatException if {@code bits} or {@code radix} is invalid
+ * @param input the string to be converted
+ * @param radix the radix (base) of the input string
+ * @return the decimal equivalent of the input string
+ * @throws NumberFormatException if the input string or radix is invalid
*/
- public static int convertToDecimal(String s, int radix) {
- int num = 0;
- int pow = 1;
+ public static int convertToDecimal(String input, int radix) {
+ int result = 0;
+ int power = 1;
- for (int i = s.length() - 1; i >= 0; i--) {
- int digit = valOfChar(s.charAt(i));
+ for (int i = input.length() - 1; i >= 0; i--) {
+ int digit = valOfChar(input.charAt(i));
if (digit >= radix) {
- throw new NumberFormatException("For input string " + s);
+ throw new NumberFormatException("For input string: " + input);
}
- num += valOfChar(s.charAt(i)) * pow;
- pow *= radix;
+ result += digit * power;
+ power *= radix;
}
- return num;
+ return result;
}
/**
- * Convert character to integer
+ * Convert a character to its integer value.
*
- * @param c the character
- * @return represented digit of given character
- * @throws NumberFormatException if {@code ch} is not UpperCase or Digit
- * character.
+ * @param character the character to be converted
+ * @return the integer value represented by the character
+ * @throws NumberFormatException if the character is not an uppercase letter or a digit
*/
- public static int valOfChar(char c) {
- if (!(Character.isUpperCase(c) || Character.isDigit(c))) {
- throw new NumberFormatException("invalid character :" + c);
+ private static int valOfChar(char character) {
+ if (Character.isDigit(character)) {
+ return character - CHAR_OFFSET_FOR_DIGIT;
+ } else if (Character.isUpperCase(character)) {
+ return character - CHAR_OFFSET_FOR_UPPERCASE;
+ } else {
+ throw new NumberFormatException("invalid character:" + character);
}
- return Character.isDigit(c) ? c - '0' : c - 'A' + 10;
}
}
diff --git a/src/main/java/com/thealgorithms/conversions/AnytoAny.java b/src/main/java/com/thealgorithms/conversions/AnytoAny.java
index 801e493032e0..e7bdbc2b79c4 100644
--- a/src/main/java/com/thealgorithms/conversions/AnytoAny.java
+++ b/src/main/java/com/thealgorithms/conversions/AnytoAny.java
@@ -1,35 +1,68 @@
package com.thealgorithms.conversions;
-import java.util.Scanner;
-
-// given a source number , source base, destination base, this code can give you the destination
-// number.
-// sn ,sb,db ---> ()dn . this is what we have to do .
-
+/**
+ * A utility class for converting numbers from any base to any other base.
+ *
+ * This class provides a method to convert a source number from a given base
+ * to a destination number in another base. Valid bases range from 2 to 10.
+ */
public final class AnytoAny {
private AnytoAny() {
}
- public static void main(String[] args) {
- Scanner scn = new Scanner(System.in);
- int sn = scn.nextInt();
- int sb = scn.nextInt();
- int db = scn.nextInt();
- int m = 1;
- int dec = 0;
- int dn = 0;
- while (sn != 0) {
- dec = dec + (sn % 10) * m;
- m *= sb;
- sn /= 10;
+ /**
+ * Converts a number from a source base to a destination base.
+ *
+ * @param sourceNumber The number in the source base (as an integer).
+ * @param sourceBase The base of the source number (between 2 and 10).
+ * @param destBase The base to which the number should be converted (between 2 and 10).
+ * @throws IllegalArgumentException if the bases are not between 2 and 10.
+ * @return The converted number in the destination base (as an integer).
+ */
+ public static int convertBase(int sourceNumber, int sourceBase, int destBase) {
+ if (sourceBase < 2 || sourceBase > 10 || destBase < 2 || destBase > 10) {
+ throw new IllegalArgumentException("Bases must be between 2 and 10.");
+ }
+
+ int decimalValue = toDecimal(sourceNumber, sourceBase);
+ return fromDecimal(decimalValue, destBase);
+ }
+
+ /**
+ * Converts a number from a given base to its decimal representation (base 10).
+ *
+ * @param number The number in the original base.
+ * @param base The base of the given number.
+ * @return The decimal representation of the number.
+ */
+ private static int toDecimal(int number, int base) {
+ int decimalValue = 0;
+ int multiplier = 1;
+
+ while (number != 0) {
+ decimalValue += (number % 10) * multiplier;
+ multiplier *= base;
+ number /= 10;
}
- m = 1;
- while (dec != 0) {
- dn = dn + (dec % db) * m;
- m *= 10;
- dec /= db;
+ return decimalValue;
+ }
+
+ /**
+ * Converts a decimal (base 10) number to a specified base.
+ *
+ * @param decimal The decimal number to convert.
+ * @param base The destination base for conversion.
+ * @return The number in the specified base.
+ */
+ private static int fromDecimal(int decimal, int base) {
+ int result = 0;
+ int multiplier = 1;
+
+ while (decimal != 0) {
+ result += (decimal % base) * multiplier;
+ multiplier *= 10;
+ decimal /= base;
}
- System.out.println(dn);
- scn.close();
+ return result;
}
}
diff --git a/src/main/java/com/thealgorithms/conversions/BinaryToDecimal.java b/src/main/java/com/thealgorithms/conversions/BinaryToDecimal.java
index 67b815ab6466..36c0790e565f 100644
--- a/src/main/java/com/thealgorithms/conversions/BinaryToDecimal.java
+++ b/src/main/java/com/thealgorithms/conversions/BinaryToDecimal.java
@@ -1,37 +1,33 @@
package com.thealgorithms.conversions;
-import java.util.Scanner;
-
/**
* This class converts a Binary number to a Decimal number
*/
final class BinaryToDecimal {
- private BinaryToDecimal() {
- }
+ private static final int BINARY_BASE = 2;
- public static long binaryToDecimal(long binNum) {
- long binCopy;
- long d;
- long s = 0;
- long power = 0;
- binCopy = binNum;
- while (binCopy != 0) {
- d = binCopy % 10;
- s += d * (long) Math.pow(2, power++);
- binCopy /= 10;
- }
- return s;
+ private BinaryToDecimal() {
}
/**
- * Main Method
+ * Converts a binary number to its decimal equivalent.
*
- * @param args Command line arguments
+ * @param binaryNumber The binary number to convert.
+ * @return The decimal equivalent of the binary number.
+ * @throws IllegalArgumentException If the binary number contains digits other than 0 and 1.
*/
- public static void main(String[] args) {
- Scanner sc = new Scanner(System.in);
- System.out.print("Binary number: ");
- System.out.println("Decimal equivalent:" + binaryToDecimal(sc.nextLong()));
- sc.close();
+ public static long binaryToDecimal(long binaryNumber) {
+ long decimalValue = 0;
+ long power = 0;
+
+ while (binaryNumber != 0) {
+ long digit = binaryNumber % 10;
+ if (digit > 1) {
+ throw new IllegalArgumentException("Incorrect binary digit: " + digit);
+ }
+ decimalValue += (long) (digit * Math.pow(BINARY_BASE, power++));
+ binaryNumber /= 10;
+ }
+ return decimalValue;
}
}
diff --git a/src/main/java/com/thealgorithms/conversions/BinaryToHexadecimal.java b/src/main/java/com/thealgorithms/conversions/BinaryToHexadecimal.java
index a19baba39715..9ff2f593fe1f 100644
--- a/src/main/java/com/thealgorithms/conversions/BinaryToHexadecimal.java
+++ b/src/main/java/com/thealgorithms/conversions/BinaryToHexadecimal.java
@@ -1,7 +1,7 @@
package com.thealgorithms.conversions;
import java.util.HashMap;
-import java.util.Scanner;
+import java.util.Map;
/**
* Converts any Binary Number to a Hexadecimal Number
@@ -9,52 +9,55 @@
* @author Nishita Aggarwal
*/
public final class BinaryToHexadecimal {
+ private static final int BITS_IN_HEX_DIGIT = 4;
+ private static final int BASE_BINARY = 2;
+ private static final int BASE_DECIMAL = 10;
+ private static final int HEX_START_DECIMAL = 10;
+ private static final int HEX_END_DECIMAL = 15;
+
private BinaryToHexadecimal() {
}
/**
- * This method converts a binary number to a hexadecimal number.
+ * Converts a binary number to a hexadecimal number.
*
- * @param binary The binary number
- * @return The hexadecimal number
+ * @param binary The binary number to convert.
+ * @return The hexadecimal representation of the binary number.
+ * @throws IllegalArgumentException If the binary number contains digits other than 0 and 1.
*/
- static String binToHex(int binary) {
- // hm to store hexadecimal codes for binary numbers within the range: 0000 to 1111 i.e. for
- // decimal numbers 0 to 15
- HashMap hm = new HashMap<>();
- // String to store hexadecimal code
- String hex = "";
- int i;
- for (i = 0; i < 10; i++) {
- hm.put(i, String.valueOf(i));
- }
- for (i = 10; i < 16; i++) {
- hm.put(i, String.valueOf((char) ('A' + i - 10)));
- }
- int currbit;
+ public static String binToHex(int binary) {
+ Map hexMap = initializeHexMap();
+ StringBuilder hex = new StringBuilder();
+
while (binary != 0) {
- int code4 = 0; // to store decimal equivalent of number formed by 4 decimal digits
- for (i = 0; i < 4; i++) {
- currbit = binary % 10;
- binary = binary / 10;
- code4 += currbit * (int) Math.pow(2, i);
+ int decimalValue = 0;
+ for (int i = 0; i < BITS_IN_HEX_DIGIT; i++) {
+ int currentBit = binary % BASE_DECIMAL;
+ if (currentBit > 1) {
+ throw new IllegalArgumentException("Incorrect binary digit: " + currentBit);
+ }
+ binary /= BASE_DECIMAL;
+ decimalValue += (int) (currentBit * Math.pow(BASE_BINARY, i));
}
- hex = hm.get(code4) + hex;
+ hex.insert(0, hexMap.get(decimalValue));
}
- return hex;
+
+ return !hex.isEmpty() ? hex.toString() : "0";
}
/**
- * Main method
+ * Initializes the hexadecimal map with decimal to hexadecimal mappings.
*
- * @param args Command line arguments
+ * @return The initialized map containing mappings from decimal numbers to hexadecimal digits.
*/
- public static void main(String[] args) {
- Scanner sc = new Scanner(System.in);
- System.out.println("Enter binary number:");
- int binary = sc.nextInt();
- String hex = binToHex(binary);
- System.out.println("Hexadecimal Code:" + hex);
- sc.close();
+ private static Map initializeHexMap() {
+ Map hexMap = new HashMap<>();
+ for (int i = 0; i < BASE_DECIMAL; i++) {
+ hexMap.put(i, String.valueOf(i));
+ }
+ for (int i = HEX_START_DECIMAL; i <= HEX_END_DECIMAL; i++) {
+ hexMap.put(i, String.valueOf((char) ('A' + i - HEX_START_DECIMAL)));
+ }
+ return hexMap;
}
}
diff --git a/src/main/java/com/thealgorithms/conversions/BinaryToOctal.java b/src/main/java/com/thealgorithms/conversions/BinaryToOctal.java
index 6fef090287ab..5407c8525a23 100644
--- a/src/main/java/com/thealgorithms/conversions/BinaryToOctal.java
+++ b/src/main/java/com/thealgorithms/conversions/BinaryToOctal.java
@@ -1,27 +1,11 @@
package com.thealgorithms.conversions;
-import java.util.Scanner;
-
-/**
- * Converts any Binary number to an Octal Number
- *
- * @author Zachary Jones
- */
public final class BinaryToOctal {
- private BinaryToOctal() {
- }
+ private static final int BITS_PER_OCTAL_DIGIT = 3;
+ private static final int BINARY_BASE = 2;
+ private static final int DECIMAL_BASE = 10;
- /**
- * Main method
- *
- * @param args Command line arguments
- */
- public static void main(String[] args) {
- Scanner sc = new Scanner(System.in);
- System.out.println("Input the binary number: ");
- int b = sc.nextInt();
- System.out.println("Octal equivalent: " + convertBinaryToOctal(b));
- sc.close();
+ private BinaryToOctal() {
}
/**
@@ -29,22 +13,33 @@ public static void main(String[] args) {
*
* @param binary The binary number
* @return The octal number
+ * @throws IllegalArgumentException if the input is not a valid binary number
*/
public static String convertBinaryToOctal(int binary) {
- String octal = "";
- int currBit = 0;
- int j = 1;
+ if (binary == 0) {
+ return "0";
+ }
+
+ if (!String.valueOf(binary).matches("[01]+")) {
+ throw new IllegalArgumentException("Input is not a valid binary number.");
+ }
+
+ StringBuilder octal = new StringBuilder();
+ int currentBit;
+ int bitValueMultiplier = 1;
+
while (binary != 0) {
- int code3 = 0;
- for (int i = 0; i < 3; i++) {
- currBit = binary % 10;
- binary = binary / 10;
- code3 += currBit * j;
- j *= 2;
+ int octalDigit = 0;
+ for (int i = 0; i < BITS_PER_OCTAL_DIGIT && binary != 0; i++) {
+ currentBit = binary % DECIMAL_BASE;
+ binary /= DECIMAL_BASE;
+ octalDigit += currentBit * bitValueMultiplier;
+ bitValueMultiplier *= BINARY_BASE;
}
- octal = code3 + octal;
- j = 1;
+ octal.insert(0, octalDigit);
+ bitValueMultiplier = 1; // Reset multiplier for the next group
}
- return octal;
+
+ return octal.toString();
}
}
diff --git a/src/main/java/com/thealgorithms/conversions/DecimalToAnyBase.java b/src/main/java/com/thealgorithms/conversions/DecimalToAnyBase.java
index 019c4026bfb5..a5615dc002f5 100644
--- a/src/main/java/com/thealgorithms/conversions/DecimalToAnyBase.java
+++ b/src/main/java/com/thealgorithms/conversions/DecimalToAnyBase.java
@@ -1,69 +1,69 @@
package com.thealgorithms.conversions;
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
import java.util.ArrayList;
+import java.util.List;
/**
+ * Class that provides methods to convert a decimal number to a string representation
+ * in any specified base between 2 and 36.
+ *
* @author Varun Upadhyay (...)
*/
-// Driver Program
public final class DecimalToAnyBase {
- private DecimalToAnyBase() {
- }
-
- public static void main(String[] args) throws Exception {
- BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
- System.out.println("Enter the decimal input below: ");
- int decInput = Integer.parseInt(br.readLine());
- System.out.println();
-
- System.out.println("Enter the base below: ");
- int base = Integer.parseInt(br.readLine());
- System.out.println();
+ private static final int MIN_BASE = 2;
+ private static final int MAX_BASE = 36;
+ private static final char ZERO_CHAR = '0';
+ private static final char A_CHAR = 'A';
+ private static final int DIGIT_OFFSET = 10;
- System.out.println("Decimal Input"
- + " is: " + decInput);
- System.out.println("Value of " + decInput + " in base " + base + " is: " + convertToAnyBase(decInput, base));
-
- br.close();
+ private DecimalToAnyBase() {
}
/**
- * This method produces a String value of any given input decimal in any
- * base
+ * Converts a decimal number to a string representation in the specified base.
+ * For example, converting the decimal number 10 to base 2 would return "1010".
*
- * @param inp Decimal of which we need the value in base in String format
- * @return string format of the converted value in the given base
+ * @param decimal the decimal number to convert
+ * @param base the base to convert to (must be between {@value #MIN_BASE} and {@value #MAX_BASE})
+ * @return the string representation of the number in the specified base
+ * @throws IllegalArgumentException if the base is out of the supported range
*/
- public static String convertToAnyBase(int inp, int base) {
- ArrayList charArr = new ArrayList<>();
+ public static String convertToAnyBase(int decimal, int base) {
+ if (base < MIN_BASE || base > MAX_BASE) {
+ throw new IllegalArgumentException("Base must be between " + MIN_BASE + " and " + MAX_BASE);
+ }
- while (inp > 0) {
- charArr.add(reVal(inp % base));
- inp /= base;
+ if (decimal == 0) {
+ return String.valueOf(ZERO_CHAR);
}
- StringBuilder str = new StringBuilder(charArr.size());
+ List digits = new ArrayList<>();
+ while (decimal > 0) {
+ digits.add(convertToChar(decimal % base));
+ decimal /= base;
+ }
- for (Character ch : charArr) {
- str.append(ch);
+ StringBuilder result = new StringBuilder(digits.size());
+ for (int i = digits.size() - 1; i >= 0; i--) {
+ result.append(digits.get(i));
}
- return str.reverse().toString();
+ return result.toString();
}
/**
- * This method produces character value of the input integer and returns it
+ * Converts an integer value to its corresponding character in the specified base.
+ * This method is used to convert values from 0 to 35 into their appropriate character representation.
+ * For example, 0-9 are represented as '0'-'9', and 10-35 are represented as 'A'-'Z'.
*
- * @param num integer of which we need the character value of
- * @return character value of input integer
+ * @param value the integer value to convert (should be less than the base value)
+ * @return the character representing the value in the specified base
*/
- public static char reVal(int num) {
- if (num >= 0 && num <= 9) {
- return (char) (num + '0');
+ private static char convertToChar(int value) {
+ if (value >= 0 && value <= 9) {
+ return (char) (ZERO_CHAR + value);
} else {
- return (char) (num - 10 + 'A');
+ return (char) (A_CHAR + value - DIGIT_OFFSET);
}
}
}
diff --git a/src/main/java/com/thealgorithms/conversions/DecimalToBinary.java b/src/main/java/com/thealgorithms/conversions/DecimalToBinary.java
index 471724ff9966..e8d033e0093c 100644
--- a/src/main/java/com/thealgorithms/conversions/DecimalToBinary.java
+++ b/src/main/java/com/thealgorithms/conversions/DecimalToBinary.java
@@ -1,63 +1,49 @@
package com.thealgorithms.conversions;
-import java.util.Scanner;
-
/**
- * This class converts a Decimal number to a Binary number
+ * This class provides methods to convert a decimal number to a binary number.
*/
final class DecimalToBinary {
+ private static final int BINARY_BASE = 2;
+ private static final int DECIMAL_MULTIPLIER = 10;
+
private DecimalToBinary() {
}
/**
- * Main Method
- *
- * @param args Command Line Arguments
+ * Converts a decimal number to a binary number using a conventional algorithm.
+ * @param decimalNumber the decimal number to convert
+ * @return the binary representation of the decimal number
*/
- public static void main(String[] args) {
- conventionalConversion();
- bitwiseConversion();
- }
+ public static int convertUsingConventionalAlgorithm(int decimalNumber) {
+ int binaryNumber = 0;
+ int position = 1;
- /**
- * This method converts a decimal number to a binary number using a
- * conventional algorithm.
- */
- public static void conventionalConversion() {
- int n;
- int b = 0;
- int c = 0;
- int d;
- Scanner input = new Scanner(System.in);
- System.out.printf("Conventional conversion.%n Enter the decimal number: ");
- n = input.nextInt();
- while (n != 0) {
- d = n % 2;
- b = b + d * (int) Math.pow(10, c++);
- n /= 2;
- } // converting decimal to binary
- System.out.println("\tBinary number: " + b);
- input.close();
+ while (decimalNumber > 0) {
+ int remainder = decimalNumber % BINARY_BASE;
+ binaryNumber += remainder * position;
+ position *= DECIMAL_MULTIPLIER;
+ decimalNumber /= BINARY_BASE;
+ }
+
+ return binaryNumber;
}
/**
- * This method converts a decimal number to a binary number using a bitwise
- * algorithm
+ * Converts a decimal number to a binary number using a bitwise algorithm.
+ * @param decimalNumber the decimal number to convert
+ * @return the binary representation of the decimal number
*/
- public static void bitwiseConversion() {
- int n;
- int b = 0;
- int c = 0;
- int d;
- Scanner input = new Scanner(System.in);
- System.out.printf("Bitwise conversion.%n Enter the decimal number: ");
- n = input.nextInt();
- while (n != 0) {
- d = (n & 1);
- b += d * (int) Math.pow(10, c++);
- n >>= 1;
+ public static int convertUsingBitwiseAlgorithm(int decimalNumber) {
+ int binaryNumber = 0;
+ int position = 1;
+
+ while (decimalNumber > 0) {
+ int leastSignificantBit = decimalNumber & 1;
+ binaryNumber += leastSignificantBit * position;
+ position *= DECIMAL_MULTIPLIER;
+ decimalNumber >>= 1;
}
- System.out.println("\tBinary number: " + b);
- input.close();
+ return binaryNumber;
}
}
diff --git a/src/main/java/com/thealgorithms/conversions/DecimalToHexaDecimal.java b/src/main/java/com/thealgorithms/conversions/DecimalToHexaDecimal.java
deleted file mode 100644
index 78838c6107b7..000000000000
--- a/src/main/java/com/thealgorithms/conversions/DecimalToHexaDecimal.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package com.thealgorithms.conversions;
-
-// hex = [0 - 9] -> [A - F]
-final class DecimalToHexaDecimal {
- private DecimalToHexaDecimal() {
- }
-
- private static final int SIZE_OF_INT_IN_HALF_BYTES = 8;
- private static final int NUMBER_OF_BITS_IN_HALF_BYTE = 4;
- private static final int HALF_BYTE = 0x0F;
- private static final char[] HEX_DIGITS = {
- '0',
- '1',
- '2',
- '3',
- '4',
- '5',
- '6',
- '7',
- '8',
- '9',
- 'A',
- 'B',
- 'C',
- 'D',
- 'E',
- 'F',
- };
-
- // Returns the hex value of the dec entered in the parameter.
- public static String decToHex(int dec) {
- StringBuilder hexBuilder = new StringBuilder(SIZE_OF_INT_IN_HALF_BYTES);
- hexBuilder.setLength(SIZE_OF_INT_IN_HALF_BYTES);
- for (int i = SIZE_OF_INT_IN_HALF_BYTES - 1; i >= 0; --i) {
- int j = dec & HALF_BYTE;
- hexBuilder.setCharAt(i, HEX_DIGITS[j]);
- dec >>= NUMBER_OF_BITS_IN_HALF_BYTE;
- }
- return hexBuilder.toString().toLowerCase();
- }
-
- // Test above function.
- public static void main(String[] args) {
- System.out.println("Test...");
- int dec = 305445566;
- String libraryDecToHex = Integer.toHexString(dec);
- String decToHex = decToHex(dec);
- System.out.println("Result from the library : " + libraryDecToHex);
- System.out.println("Result decToHex method : " + decToHex);
- }
-}
diff --git a/src/main/java/com/thealgorithms/conversions/DecimalToHexadecimal.java b/src/main/java/com/thealgorithms/conversions/DecimalToHexadecimal.java
new file mode 100644
index 000000000000..47a1e36b27e3
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/DecimalToHexadecimal.java
@@ -0,0 +1,42 @@
+package com.thealgorithms.conversions;
+
+/**
+ * This class provides a method to convert a decimal number to a hexadecimal string.
+ */
+final class DecimalToHexadecimal {
+ private static final int SIZE_OF_INT_IN_HALF_BYTES = 8;
+ private static final int NUMBER_OF_BITS_IN_HALF_BYTE = 4;
+ private static final int HALF_BYTE_MASK = 0x0F;
+ private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+ private DecimalToHexadecimal() {
+ }
+
+ /**
+ * Converts a decimal number to a hexadecimal string.
+ * @param decimal the decimal number to convert
+ * @return the hexadecimal representation of the decimal number
+ */
+ public static String decToHex(int decimal) {
+ StringBuilder hexBuilder = new StringBuilder(SIZE_OF_INT_IN_HALF_BYTES);
+ for (int i = SIZE_OF_INT_IN_HALF_BYTES - 1; i >= 0; --i) {
+ int currentHalfByte = decimal & HALF_BYTE_MASK;
+ hexBuilder.insert(0, HEX_DIGITS[currentHalfByte]);
+ decimal >>= NUMBER_OF_BITS_IN_HALF_BYTE;
+ }
+ return removeLeadingZeros(hexBuilder.toString().toLowerCase());
+ }
+
+ private static String removeLeadingZeros(String str) {
+ if (str == null || str.isEmpty()) {
+ return str;
+ }
+
+ int i = 0;
+ while (i < str.length() && str.charAt(i) == '0') {
+ i++;
+ }
+
+ return i == str.length() ? "0" : str.substring(i);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/conversions/DecimalToOctal.java b/src/main/java/com/thealgorithms/conversions/DecimalToOctal.java
index 4bc3a6e7af8c..75687fc589ae 100644
--- a/src/main/java/com/thealgorithms/conversions/DecimalToOctal.java
+++ b/src/main/java/com/thealgorithms/conversions/DecimalToOctal.java
@@ -1,38 +1,38 @@
package com.thealgorithms.conversions;
-import java.util.Scanner;
-
/**
* This class converts Decimal numbers to Octal Numbers
*/
public final class DecimalToOctal {
+ private static final int OCTAL_BASE = 8;
+ private static final int INITIAL_OCTAL_VALUE = 0;
+ private static final int INITIAL_PLACE_VALUE = 1;
+
private DecimalToOctal() {
}
/**
- * Main Method
+ * Converts a decimal number to its octal equivalent.
*
- * @param args Command line Arguments
+ * @param decimal The decimal number to convert.
+ * @return The octal equivalent as an integer.
+ * @throws IllegalArgumentException if the decimal number is negative.
*/
+ public static int convertToOctal(int decimal) {
+ if (decimal < 0) {
+ throw new IllegalArgumentException("Decimal number cannot be negative.");
+ }
+
+ int octal = INITIAL_OCTAL_VALUE;
+ int placeValue = INITIAL_PLACE_VALUE;
- // enter in a decimal value to get Octal output
- public static void main(String[] args) {
- Scanner sc = new Scanner(System.in);
- int n;
- int k;
- int d;
- int s = 0;
- int c = 0;
- System.out.print("Decimal number: ");
- n = sc.nextInt();
- k = n;
- while (k != 0) {
- d = k % 8;
- s += d * (int) Math.pow(10, c++);
- k /= 8;
+ while (decimal != 0) {
+ int remainder = decimal % OCTAL_BASE;
+ octal += remainder * placeValue;
+ decimal /= OCTAL_BASE;
+ placeValue *= 10;
}
- System.out.println("Octal equivalent:" + s);
- sc.close();
+ return octal;
}
}
diff --git a/src/main/java/com/thealgorithms/conversions/EndianConverter.java b/src/main/java/com/thealgorithms/conversions/EndianConverter.java
new file mode 100644
index 000000000000..0d69098e8255
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/EndianConverter.java
@@ -0,0 +1,47 @@
+package com.thealgorithms.conversions;
+
+/**
+ * Utility class for converting integers between big-endian and little-endian formats.
+ *
+ * Endianness defines how byte sequences represent multi-byte data types:
+ *
+ * - Big-endian: The most significant byte (MSB) comes first.
+ * - Little-endian: The least significant byte (LSB) comes first.
+ *
+ *
+ * Example conversion:
+ *
+ * - Big-endian to little-endian: {@code 0x12345678} → {@code 0x78563412}
+ * - Little-endian to big-endian: {@code 0x78563412} → {@code 0x12345678}
+ *
+ *
+ * Note: Both conversions in this utility are equivalent since reversing the bytes is symmetric.
+ *
+ * This class only supports 32-bit integers.
+ *
+ * @author Hardvan
+ */
+public final class EndianConverter {
+ private EndianConverter() {
+ }
+
+ /**
+ * Converts a 32-bit integer from big-endian to little-endian.
+ *
+ * @param value the integer in big-endian format
+ * @return the integer in little-endian format
+ */
+ public static int bigToLittleEndian(int value) {
+ return Integer.reverseBytes(value);
+ }
+
+ /**
+ * Converts a 32-bit integer from little-endian to big-endian.
+ *
+ * @param value the integer in little-endian format
+ * @return the integer in big-endian format
+ */
+ public static int littleToBigEndian(int value) {
+ return Integer.reverseBytes(value);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/conversions/HexToOct.java b/src/main/java/com/thealgorithms/conversions/HexToOct.java
index 97a8be16b2e0..d3a672d37424 100644
--- a/src/main/java/com/thealgorithms/conversions/HexToOct.java
+++ b/src/main/java/com/thealgorithms/conversions/HexToOct.java
@@ -1,7 +1,5 @@
package com.thealgorithms.conversions;
-import java.util.Scanner;
-
/**
* Converts any Hexadecimal Number to Octal
*
@@ -12,64 +10,53 @@ private HexToOct() {
}
/**
- * This method converts a Hexadecimal number to a decimal number
+ * Converts a Hexadecimal number to a Decimal number.
*
- * @param s The Hexadecimal Number
- * @return The Decimal number
+ * @param hex The Hexadecimal number as a String.
+ * @return The Decimal equivalent as an integer.
*/
- public static int hex2decimal(String s) {
- String str = "0123456789ABCDEF";
- s = s.toUpperCase();
- int val = 0;
- for (int i = 0; i < s.length(); i++) {
- char a = s.charAt(i);
- int n = str.indexOf(a);
- val = 16 * val + n;
+ public static int hexToDecimal(String hex) {
+ String hexDigits = "0123456789ABCDEF";
+ hex = hex.toUpperCase();
+ int decimalValue = 0;
+
+ for (int i = 0; i < hex.length(); i++) {
+ char hexChar = hex.charAt(i);
+ int digitValue = hexDigits.indexOf(hexChar);
+ decimalValue = 16 * decimalValue + digitValue;
}
- return val;
+
+ return decimalValue;
}
/**
- * This method converts a Decimal number to a octal number
+ * Converts a Decimal number to an Octal number.
*
- * @param q The Decimal Number
- * @return The Octal number
+ * @param decimal The Decimal number as an integer.
+ * @return The Octal equivalent as an integer.
*/
- public static int decimal2octal(int q) {
- int now;
- int i = 1;
- int octnum = 0;
- while (q > 0) {
- now = q % 8;
- octnum = (now * (int) (Math.pow(10, i))) + octnum;
- q /= 8;
- i++;
+ public static int decimalToOctal(int decimal) {
+ int octalValue = 0;
+ int placeValue = 1;
+
+ while (decimal > 0) {
+ int remainder = decimal % 8;
+ octalValue += remainder * placeValue;
+ decimal /= 8;
+ placeValue *= 10;
}
- octnum /= 10;
- return octnum;
+
+ return octalValue;
}
/**
- * Main method that gets the hex input from user and converts it into octal.
+ * Converts a Hexadecimal number to an Octal number.
*
- * @param args arguments
+ * @param hex The Hexadecimal number as a String.
+ * @return The Octal equivalent as an integer.
*/
- public static void main(String[] args) {
- String hexadecnum;
- int decnum;
- int octalnum;
- Scanner scan = new Scanner(System.in);
-
- System.out.print("Enter Hexadecimal Number : ");
- hexadecnum = scan.nextLine();
-
- // first convert hexadecimal to decimal
- decnum = hex2decimal(hexadecnum); // Pass the string to the hex2decimal function and get the decimal form in
- // variable decnum
-
- // convert decimal to octal
- octalnum = decimal2octal(decnum);
- System.out.println("Number in octal: " + octalnum);
- scan.close();
+ public static int hexToOctal(String hex) {
+ int decimalValue = hexToDecimal(hex);
+ return decimalToOctal(decimalValue);
}
}
diff --git a/src/main/java/com/thealgorithms/conversions/HexaDecimalToBinary.java b/src/main/java/com/thealgorithms/conversions/HexaDecimalToBinary.java
index b6228488dc76..c0eb9a01ba17 100644
--- a/src/main/java/com/thealgorithms/conversions/HexaDecimalToBinary.java
+++ b/src/main/java/com/thealgorithms/conversions/HexaDecimalToBinary.java
@@ -1,43 +1,62 @@
package com.thealgorithms.conversions;
-// Hex [0-9],[A-F] -> Binary [0,1]
+/**
+ * Utility class for converting hexadecimal numbers to binary representation.
+ *
+ * A hexadecimal number consists of digits from {@code [0-9]} and {@code [A-F]} (case-insensitive),
+ * while binary representation uses only {@code [0, 1]}.
+ *
+ * This class provides methods to:
+ *
+ * - Convert a hexadecimal string to its binary string equivalent.
+ * - Ensure the binary output is padded to 8 bits (1 byte).
+ *
+ *
+ * Example:
+ *
+ * - {@code "A1"} → {@code "10100001"}
+ * - {@code "1"} → {@code "00000001"}
+ *
+ *
+ * This class assumes that the input hexadecimal string is valid.
+ */
public class HexaDecimalToBinary {
+
+ /**
+ * Converts a hexadecimal string to its binary string equivalent.
+ * The binary output is padded to a minimum of 8 bits (1 byte).
+ * Steps:
+ *
+ * - Convert the hexadecimal string to an integer.
+ * - Convert the integer to a binary string.
+ * - Pad the binary string to ensure it is at least 8 bits long.
+ * - Return the padded binary string.
+ *
+ *
+ * @param numHex the hexadecimal string (e.g., "A1", "7F")
+ * @throws NumberFormatException if the input string is not a valid hexadecimal number
+ * @return the binary string representation, padded to 8 bits (e.g., "10100001")
+ */
public String convert(String numHex) {
- // String a HexaDecimal:
int conHex = Integer.parseInt(numHex, 16);
- // Hex a Binary:
String binary = Integer.toBinaryString(conHex);
- // Output:
return completeDigits(binary);
}
+ /**
+ * Pads the binary string to ensure it is at least 8 bits long.
+ * If the binary string is shorter than 8 bits, it adds leading zeros.
+ *
+ * @param binNum the binary string to pad
+ * @return the padded binary string with a minimum length of 8
+ */
public String completeDigits(String binNum) {
- final int longBits = 8;
- for (int i = binNum.length(); i < longBits; i++) {
- binNum = "0" + binNum;
+ final int byteSize = 8;
+ StringBuilder binNumBuilder = new StringBuilder(binNum);
+ while (binNumBuilder.length() < byteSize) {
+ binNumBuilder.insert(0, "0");
}
+ binNum = binNumBuilder.toString();
return binNum;
}
-
- public static void main(String[] args) {
- // Testing Numbers:
- String[] hexNums = {
- "1",
- "A1",
- "ef",
- "BA",
- "AA",
- "BB",
- "19",
- "01",
- "02",
- "03",
- "04",
- };
- HexaDecimalToBinary objConvert = new HexaDecimalToBinary();
-
- for (String num : hexNums) {
- System.out.println(num + " = " + objConvert.convert(num));
- }
- }
}
diff --git a/src/main/java/com/thealgorithms/conversions/HexaDecimalToDecimal.java b/src/main/java/com/thealgorithms/conversions/HexaDecimalToDecimal.java
index 003781da9d5e..2cf6024d90a3 100644
--- a/src/main/java/com/thealgorithms/conversions/HexaDecimalToDecimal.java
+++ b/src/main/java/com/thealgorithms/conversions/HexaDecimalToDecimal.java
@@ -1,39 +1,45 @@
package com.thealgorithms.conversions;
-import java.util.Scanner;
-
+/**
+ * Utility class for converting a hexadecimal string to its decimal representation.
+ *
+ * A hexadecimal number uses the base-16 numeral system, with the following characters:
+ *
+ * - Digits: 0-9
+ * - Letters: A-F (case-insensitive)
+ *
+ * Each character represents a power of 16. For example:
+ *
+ * Hexadecimal "A1" = 10*16^1 + 1*16^0 = 161 (decimal)
+ *
+ *
+ * This class provides a method to perform the conversion without using built-in Java utilities.
+ */
public final class HexaDecimalToDecimal {
private HexaDecimalToDecimal() {
}
- // convert hexadecimal to decimal
+ /**
+ * Converts a hexadecimal string to its decimal integer equivalent.
+ * The input string is case-insensitive, and must contain valid hexadecimal characters [0-9, A-F].
+ *
+ * @param hex the hexadecimal string to convert
+ * @return the decimal integer representation of the input hexadecimal string
+ * @throws IllegalArgumentException if the input string contains invalid characters
+ */
public static int getHexaToDec(String hex) {
String digits = "0123456789ABCDEF";
hex = hex.toUpperCase();
int val = 0;
+
for (int i = 0; i < hex.length(); i++) {
int d = digits.indexOf(hex.charAt(i));
+ if (d == -1) {
+ throw new IllegalArgumentException("Invalid hexadecimal character: " + hex.charAt(i));
+ }
val = 16 * val + d;
}
- return val;
- }
- // Main method gets the hexadecimal input from user and converts it into Decimal output.
- public static void main(String[] args) {
- String hexaInput;
- int decOutput;
- Scanner scan = new Scanner(System.in);
-
- System.out.print("Enter Hexadecimal Number : ");
- hexaInput = scan.nextLine();
-
- // convert hexadecimal to decimal
- decOutput = getHexaToDec(hexaInput);
- /*
- Pass the string to the getHexaToDec function
- and it returns the decimal form in the variable decOutput.
- */
- System.out.println("Number in Decimal: " + decOutput);
- scan.close();
+ return val;
}
}
diff --git a/src/main/java/com/thealgorithms/conversions/IPConverter.java b/src/main/java/com/thealgorithms/conversions/IPConverter.java
new file mode 100644
index 000000000000..765cb0201dd5
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/IPConverter.java
@@ -0,0 +1,58 @@
+package com.thealgorithms.conversions;
+
+/**
+ * Converts an IPv4 address to its binary equivalent and vice-versa.
+ * IP to Binary: Converts an IPv4 address to its binary equivalent.
+ * Example: 127.3.4.5 -> 01111111.00000011.00000100.00000101
+ *
+ * Binary to IP: Converts a binary equivalent to an IPv4 address.
+ * Example: 01111111.00000011.00000100.00000101 -> 127.3.4.5
+ *
+ * @author Hardvan
+ */
+public final class IPConverter {
+ private IPConverter() {
+ }
+
+ /**
+ * Converts an IPv4 address to its binary equivalent.
+ * @param ip The IPv4 address to convert.
+ * @return The binary equivalent of the IPv4 address.
+ */
+ public static String ipToBinary(String ip) {
+ StringBuilder binary = new StringBuilder();
+ for (String octet : ip.split("\\.")) {
+ binary.append(octetToBinary(Integer.parseInt(octet))).append(".");
+ }
+ return binary.substring(0, binary.length() - 1);
+ }
+
+ /**
+ * Converts a single octet to its 8-bit binary representation.
+ * @param octet The octet to convert (0-255).
+ * @return The 8-bit binary representation as a String.
+ */
+ private static String octetToBinary(int octet) {
+ char[] binary = {'0', '0', '0', '0', '0', '0', '0', '0'};
+ for (int i = 7; i >= 0; i--) {
+ if ((octet & 1) == 1) {
+ binary[i] = '1';
+ }
+ octet >>>= 1;
+ }
+ return new String(binary);
+ }
+
+ /**
+ * Converts a binary equivalent to an IPv4 address.
+ * @param binary The binary equivalent to convert.
+ * @return The IPv4 address of the binary equivalent.
+ */
+ public static String binaryToIP(String binary) {
+ StringBuilder ip = new StringBuilder();
+ for (String octet : binary.split("\\.")) {
+ ip.append(Integer.parseInt(octet, 2)).append(".");
+ }
+ return ip.substring(0, ip.length() - 1);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/conversions/IPv6Converter.java b/src/main/java/com/thealgorithms/conversions/IPv6Converter.java
new file mode 100644
index 000000000000..d42ffd027514
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/IPv6Converter.java
@@ -0,0 +1,98 @@
+package com.thealgorithms.conversions;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+/**
+ * A utility class for converting between IPv6 and IPv4 addresses.
+ *
+ * - Converts IPv4 to IPv6-mapped IPv6 address.
+ * - Extracts IPv4 address from IPv6-mapped IPv6.
+ * - Handles exceptions for invalid inputs.
+ *
+ * @author Hardvan
+ */
+public final class IPv6Converter {
+ private IPv6Converter() {
+ }
+
+ /**
+ * Converts an IPv4 address (e.g., "192.0.2.128") to an IPv6-mapped IPv6 address.
+ * Example: IPv4 "192.0.2.128" -> IPv6 "::ffff:192.0.2.128"
+ *
+ * @param ipv4Address The IPv4 address in string format.
+ * @return The corresponding IPv6-mapped IPv6 address.
+ * @throws UnknownHostException If the IPv4 address is invalid.
+ * @throws IllegalArgumentException If the IPv6 address is not a mapped IPv4 address.
+ */
+ public static String ipv4ToIpv6(String ipv4Address) throws UnknownHostException {
+ if (ipv4Address == null || ipv4Address.isEmpty()) {
+ throw new UnknownHostException("IPv4 address is empty.");
+ }
+
+ InetAddress ipv4 = InetAddress.getByName(ipv4Address);
+ byte[] ipv4Bytes = ipv4.getAddress();
+
+ // Create IPv6-mapped IPv6 address (starts with ::ffff:)
+ byte[] ipv6Bytes = new byte[16];
+ ipv6Bytes[10] = (byte) 0xff;
+ ipv6Bytes[11] = (byte) 0xff;
+ System.arraycopy(ipv4Bytes, 0, ipv6Bytes, 12, 4);
+
+ // Manually format to "::ffff:x.x.x.x" format
+ StringBuilder ipv6String = new StringBuilder("::ffff:");
+ for (int i = 12; i < 16; i++) {
+ ipv6String.append(ipv6Bytes[i] & 0xFF);
+ if (i < 15) {
+ ipv6String.append('.');
+ }
+ }
+ return ipv6String.toString();
+ }
+
+ /**
+ * Extracts the IPv4 address from an IPv6-mapped IPv6 address.
+ * Example: IPv6 "::ffff:192.0.2.128" -> IPv4 "192.0.2.128"
+ *
+ * @param ipv6Address The IPv6 address in string format.
+ * @return The extracted IPv4 address.
+ * @throws UnknownHostException If the IPv6 address is invalid or not a mapped IPv4 address.
+ */
+ public static String ipv6ToIpv4(String ipv6Address) throws UnknownHostException {
+ InetAddress ipv6 = InetAddress.getByName(ipv6Address);
+ byte[] ipv6Bytes = ipv6.getAddress();
+
+ // Check if the address is an IPv6-mapped IPv4 address
+ if (isValidIpv6MappedIpv4(ipv6Bytes)) {
+ byte[] ipv4Bytes = Arrays.copyOfRange(ipv6Bytes, 12, 16);
+ InetAddress ipv4 = InetAddress.getByAddress(ipv4Bytes);
+ return ipv4.getHostAddress();
+ } else {
+ throw new IllegalArgumentException("Not a valid IPv6-mapped IPv4 address.");
+ }
+ }
+
+ /**
+ * Helper function to check if the given byte array represents
+ * an IPv6-mapped IPv4 address (prefix 0:0:0:0:0:ffff).
+ *
+ * @param ipv6Bytes Byte array representation of the IPv6 address.
+ * @return True if the address is IPv6-mapped IPv4, otherwise false.
+ */
+ private static boolean isValidIpv6MappedIpv4(byte[] ipv6Bytes) {
+ // IPv6-mapped IPv4 addresses are 16 bytes long, with the first 10 bytes set to 0,
+ // followed by 0xff, 0xff, and the last 4 bytes representing the IPv4 address.
+ if (ipv6Bytes.length != 16) {
+ return false;
+ }
+
+ for (int i = 0; i < 10; i++) {
+ if (ipv6Bytes[i] != 0) {
+ return false;
+ }
+ }
+
+ return ipv6Bytes[10] == (byte) 0xff && ipv6Bytes[11] == (byte) 0xff;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/conversions/IntegerToEnglish.java b/src/main/java/com/thealgorithms/conversions/IntegerToEnglish.java
new file mode 100644
index 000000000000..e85c608af5d0
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/IntegerToEnglish.java
@@ -0,0 +1,108 @@
+package com.thealgorithms.conversions;
+
+import java.util.Map;
+
+/**
+ * A utility class to convert integers to their English word representation.
+ *
+ * The class supports conversion of numbers from 0 to 2,147,483,647
+ * (the maximum value of a 32-bit signed integer). It divides the number
+ * into groups of three digits (thousands, millions, billions, etc.) and
+ * translates each group into words.
+ *
+ * Example Usage
+ *
+ * IntegerToEnglish.integerToEnglishWords(12345);
+ * // Output: "Twelve Thousand Three Hundred Forty Five"
+ *
+ *
+ * This class uses two maps:
+ *
+ * - BASE_NUMBERS_MAP: Holds English words for numbers 0-20, multiples of 10 up to 90, and 100.
+ * - THOUSAND_POWER_MAP: Maps powers of 1000 (e.g., Thousand, Million, Billion).
+ *
+ */
+public final class IntegerToEnglish {
+
+ private static final Map BASE_NUMBERS_MAP = Map.ofEntries(Map.entry(0, ""), Map.entry(1, "One"), Map.entry(2, "Two"), Map.entry(3, "Three"), Map.entry(4, "Four"), Map.entry(5, "Five"), Map.entry(6, "Six"), Map.entry(7, "Seven"), Map.entry(8, "Eight"), Map.entry(9, "Nine"),
+ Map.entry(10, "Ten"), Map.entry(11, "Eleven"), Map.entry(12, "Twelve"), Map.entry(13, "Thirteen"), Map.entry(14, "Fourteen"), Map.entry(15, "Fifteen"), Map.entry(16, "Sixteen"), Map.entry(17, "Seventeen"), Map.entry(18, "Eighteen"), Map.entry(19, "Nineteen"), Map.entry(20, "Twenty"),
+ Map.entry(30, "Thirty"), Map.entry(40, "Forty"), Map.entry(50, "Fifty"), Map.entry(60, "Sixty"), Map.entry(70, "Seventy"), Map.entry(80, "Eighty"), Map.entry(90, "Ninety"), Map.entry(100, "Hundred"));
+
+ private static final Map THOUSAND_POWER_MAP = Map.ofEntries(Map.entry(1, "Thousand"), Map.entry(2, "Million"), Map.entry(3, "Billion"));
+
+ private IntegerToEnglish() {
+ }
+
+ /**
+ * Converts numbers less than 1000 into English words.
+ *
+ * @param number the integer value (0-999) to convert
+ * @return the English word representation of the input number
+ */
+ private static String convertToWords(int number) {
+ int remainder = number % 100;
+ StringBuilder result = new StringBuilder();
+
+ if (remainder <= 20) {
+ result.append(BASE_NUMBERS_MAP.get(remainder));
+ } else if (BASE_NUMBERS_MAP.containsKey(remainder)) {
+ result.append(BASE_NUMBERS_MAP.get(remainder));
+ } else {
+ int tensDigit = remainder / 10;
+ int onesDigit = remainder % 10;
+ String tens = BASE_NUMBERS_MAP.getOrDefault(tensDigit * 10, "");
+ String ones = BASE_NUMBERS_MAP.getOrDefault(onesDigit, "");
+ result.append(tens);
+ if (ones != null && !ones.isEmpty()) {
+ result.append(" ").append(ones);
+ }
+ }
+
+ int hundredsDigit = number / 100;
+ if (hundredsDigit > 0) {
+ if (result.length() > 0) {
+ result.insert(0, " ");
+ }
+ result.insert(0, String.format("%s Hundred", BASE_NUMBERS_MAP.get(hundredsDigit)));
+ }
+
+ return result.toString().trim();
+ }
+
+ /**
+ * Converts a non-negative integer to its English word representation.
+ *
+ * @param number the integer to convert (0-2,147,483,647)
+ * @return the English word representation of the input number
+ */
+ public static String integerToEnglishWords(int number) {
+ if (number == 0) {
+ return "Zero";
+ }
+
+ StringBuilder result = new StringBuilder();
+ int index = 0;
+
+ while (number > 0) {
+ int remainder = number % 1000;
+ number /= 1000;
+
+ if (remainder > 0) {
+ String subResult = convertToWords(remainder);
+ if (!subResult.isEmpty()) {
+ if (index > 0) {
+ subResult += " " + THOUSAND_POWER_MAP.get(index);
+ }
+ if (result.length() > 0) {
+ result.insert(0, " ");
+ }
+ result.insert(0, subResult);
+ }
+ }
+
+ index++;
+ }
+
+ return result.toString().trim();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/conversions/IntegerToRoman.java b/src/main/java/com/thealgorithms/conversions/IntegerToRoman.java
index 9c031df9504d..fec437668fe6 100644
--- a/src/main/java/com/thealgorithms/conversions/IntegerToRoman.java
+++ b/src/main/java/com/thealgorithms/conversions/IntegerToRoman.java
@@ -1,68 +1,68 @@
package com.thealgorithms.conversions;
/**
- * Converting Integers into Roman Numerals
+ * A utility class to convert integers into Roman numerals.
*
- *
- * ('I', 1); ('IV',4); ('V', 5); ('IX',9); ('X', 10); ('XL',40); ('L', 50);
- * ('XC',90); ('C', 100); ('D', 500); ('M', 1000);
+ *
Roman numerals follow these rules:
+ *
+ * - I = 1
+ * - IV = 4
+ * - V = 5
+ * - IX = 9
+ * - X = 10
+ * - XL = 40
+ * - L = 50
+ * - XC = 90
+ * - C = 100
+ * - D = 500
+ * - M = 1000
+ *
+ *
+ * Conversion is based on repeatedly subtracting the largest possible Roman numeral value
+ * from the input number until it reaches zero. For example, 1994 is converted as:
+ *
+ * 1994 -> MCMXCIV (1000 + 900 + 90 + 4)
+ *
*/
public final class IntegerToRoman {
+
+ // Array of Roman numeral values in descending order
+ private static final int[] ALL_ROMAN_NUMBERS_IN_ARABIC = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
+
+ // Corresponding Roman numeral symbols
+ private static final String[] ALL_ROMAN_NUMBERS = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
+
private IntegerToRoman() {
}
- private static final int[] ALL_ROMAN_NUMBERS_IN_ARABIC = new int[] {
- 1000,
- 900,
- 500,
- 400,
- 100,
- 90,
- 50,
- 40,
- 10,
- 9,
- 5,
- 4,
- 1,
- };
- private static final String[] ALL_ROMAN_NUMBERS = new String[] {
- "M",
- "CM",
- "D",
- "CD",
- "C",
- "XC",
- "L",
- "XL",
- "X",
- "IX",
- "V",
- "IV",
- "I",
- };
-
- // Value must be > 0
+ /**
+ * Converts an integer to its Roman numeral representation.
+ * Steps:
+ *
+ * - Iterate over the Roman numeral values in descending order
+ * - Calculate how many times a numeral fits
+ * - Append the corresponding symbol
+ * - Subtract the value from the number
+ * - Repeat until the number is zero
+ * - Return the Roman numeral representation
+ *
+ *
+ * @param num the integer value to convert (must be greater than 0)
+ * @return the Roman numeral representation of the input integer
+ * or an empty string if the input is non-positive
+ */
public static String integerToRoman(int num) {
if (num <= 0) {
return "";
}
StringBuilder builder = new StringBuilder();
-
- for (int a = 0; a < ALL_ROMAN_NUMBERS_IN_ARABIC.length; a++) {
- int times = num / ALL_ROMAN_NUMBERS_IN_ARABIC[a];
- for (int b = 0; b < times; b++) {
- builder.append(ALL_ROMAN_NUMBERS[a]);
- }
-
- num -= times * ALL_ROMAN_NUMBERS_IN_ARABIC[a];
+ for (int i = 0; i < ALL_ROMAN_NUMBERS_IN_ARABIC.length; i++) {
+ int times = num / ALL_ROMAN_NUMBERS_IN_ARABIC[i];
+ builder.append(ALL_ROMAN_NUMBERS[i].repeat(Math.max(0, times)));
+ num -= times * ALL_ROMAN_NUMBERS_IN_ARABIC[i];
}
return builder.toString();
}
-
- public static void main(String[] args) {
- System.out.println(IntegerToRoman.integerToRoman(2131));
- }
}
diff --git a/src/main/java/com/thealgorithms/conversions/MorseCodeConverter.java b/src/main/java/com/thealgorithms/conversions/MorseCodeConverter.java
new file mode 100644
index 000000000000..a3973da0c586
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/MorseCodeConverter.java
@@ -0,0 +1,98 @@
+package com.thealgorithms.conversions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Converts text to Morse code and vice-versa.
+ * Text to Morse code: Each letter is separated by a space and each word is separated by a pipe (|).
+ * Example: "HELLO WORLD" -> ".... . .-.. .-.. --- | .-- --- .-. .-.. -.."
+ *
+ * Morse code to text: Each letter is separated by a space and each word is separated by a pipe (|).
+ * Example: ".... . .-.. .-.. --- | .-- --- .-. .-.. -.." -> "HELLO WORLD"
+ *
+ * Applications: Used in radio communications and algorithmic challenges.
+ *
+ * @author Hardvan
+ */
+public final class MorseCodeConverter {
+ private MorseCodeConverter() {
+ }
+
+ private static final Map MORSE_MAP = new HashMap<>();
+ private static final Map REVERSE_MAP = new HashMap<>();
+
+ static {
+ MORSE_MAP.put('A', ".-");
+ MORSE_MAP.put('B', "-...");
+ MORSE_MAP.put('C', "-.-.");
+ MORSE_MAP.put('D', "-..");
+ MORSE_MAP.put('E', ".");
+ MORSE_MAP.put('F', "..-.");
+ MORSE_MAP.put('G', "--.");
+ MORSE_MAP.put('H', "....");
+ MORSE_MAP.put('I', "..");
+ MORSE_MAP.put('J', ".---");
+ MORSE_MAP.put('K', "-.-");
+ MORSE_MAP.put('L', ".-..");
+ MORSE_MAP.put('M', "--");
+ MORSE_MAP.put('N', "-.");
+ MORSE_MAP.put('O', "---");
+ MORSE_MAP.put('P', ".--.");
+ MORSE_MAP.put('Q', "--.-");
+ MORSE_MAP.put('R', ".-.");
+ MORSE_MAP.put('S', "...");
+ MORSE_MAP.put('T', "-");
+ MORSE_MAP.put('U', "..-");
+ MORSE_MAP.put('V', "...-");
+ MORSE_MAP.put('W', ".--");
+ MORSE_MAP.put('X', "-..-");
+ MORSE_MAP.put('Y', "-.--");
+ MORSE_MAP.put('Z', "--..");
+
+ // Build reverse map for decoding
+ MORSE_MAP.forEach((k, v) -> REVERSE_MAP.put(v, k));
+ }
+
+ /**
+ * Converts text to Morse code.
+ * Each letter is separated by a space and each word is separated by a pipe (|).
+ *
+ * @param text The text to convert to Morse code.
+ * @return The Morse code representation of the text.
+ */
+ public static String textToMorse(String text) {
+ StringBuilder morse = new StringBuilder();
+ String[] words = text.toUpperCase().split(" ");
+ for (int i = 0; i < words.length; i++) {
+ for (char c : words[i].toCharArray()) {
+ morse.append(MORSE_MAP.getOrDefault(c, "")).append(" ");
+ }
+ if (i < words.length - 1) {
+ morse.append("| ");
+ }
+ }
+ return morse.toString().trim();
+ }
+
+ /**
+ * Converts Morse code to text.
+ * Each letter is separated by a space and each word is separated by a pipe (|).
+ *
+ * @param morse The Morse code to convert to text.
+ * @return The text representation of the Morse code.
+ */
+ public static String morseToText(String morse) {
+ StringBuilder text = new StringBuilder();
+ String[] words = morse.split(" \\| ");
+ for (int i = 0; i < words.length; i++) {
+ for (String code : words[i].split(" ")) {
+ text.append(REVERSE_MAP.getOrDefault(code, '?'));
+ }
+ if (i < words.length - 1) {
+ text.append(" ");
+ }
+ }
+ return text.toString();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/conversions/NumberToWords.java b/src/main/java/com/thealgorithms/conversions/NumberToWords.java
new file mode 100644
index 000000000000..e39c5b2dea86
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/NumberToWords.java
@@ -0,0 +1,100 @@
+package com.thealgorithms.conversions;
+
+import java.math.BigDecimal;
+
+/**
+ A Java-based utility for converting numeric values into their English word
+ representations. Whether you need to convert a small number, a large number
+ with millions and billions, or even a number with decimal places, this utility
+ has you covered.
+ *
+ */
+public final class NumberToWords {
+
+ private NumberToWords() {
+ }
+
+ private static final String[] UNITS = {"", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"};
+
+ private static final String[] TENS = {"", "", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"};
+
+ private static final String[] POWERS = {"", "Thousand", "Million", "Billion", "Trillion"};
+
+ private static final String ZERO = "Zero";
+ private static final String POINT = " Point";
+ private static final String NEGATIVE = "Negative ";
+
+ public static String convert(BigDecimal number) {
+ if (number == null) {
+ return "Invalid Input";
+ }
+
+ // Check for negative sign
+ boolean isNegative = number.signum() < 0;
+
+ // Split the number into whole and fractional parts
+ BigDecimal[] parts = number.abs().divideAndRemainder(BigDecimal.ONE);
+ BigDecimal wholePart = parts[0]; // Keep whole part as BigDecimal
+ String fractionalPartStr = parts[1].compareTo(BigDecimal.ZERO) > 0 ? parts[1].toPlainString().substring(2) : ""; // Get fractional part only if it exists
+
+ // Convert whole part to words
+ StringBuilder result = new StringBuilder();
+ if (isNegative) {
+ result.append(NEGATIVE);
+ }
+ result.append(convertWholeNumberToWords(wholePart));
+
+ // Convert fractional part to words
+ if (!fractionalPartStr.isEmpty()) {
+ result.append(POINT);
+ for (char digit : fractionalPartStr.toCharArray()) {
+ int digitValue = Character.getNumericValue(digit);
+ result.append(" ").append(digitValue == 0 ? ZERO : UNITS[digitValue]);
+ }
+ }
+
+ return result.toString().trim();
+ }
+
+ private static String convertWholeNumberToWords(BigDecimal number) {
+ if (number.compareTo(BigDecimal.ZERO) == 0) {
+ return ZERO;
+ }
+
+ StringBuilder words = new StringBuilder();
+ int power = 0;
+
+ while (number.compareTo(BigDecimal.ZERO) > 0) {
+ // Get the last three digits
+ BigDecimal[] divisionResult = number.divideAndRemainder(BigDecimal.valueOf(1000));
+ int chunk = divisionResult[1].intValue();
+
+ if (chunk > 0) {
+ String chunkWords = convertChunk(chunk);
+ if (power > 0) {
+ words.insert(0, POWERS[power] + " ");
+ }
+ words.insert(0, chunkWords + " ");
+ }
+
+ number = divisionResult[0]; // Continue with the remaining part
+ power++;
+ }
+
+ return words.toString().trim();
+ }
+
+ private static String convertChunk(int number) {
+ String chunkWords;
+
+ if (number < 20) {
+ chunkWords = UNITS[number];
+ } else if (number < 100) {
+ chunkWords = TENS[number / 10] + (number % 10 > 0 ? " " + UNITS[number % 10] : "");
+ } else {
+ chunkWords = UNITS[number / 100] + " Hundred" + (number % 100 > 0 ? " " + convertChunk(number % 100) : "");
+ }
+
+ return chunkWords;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/conversions/OctalToBinary.java b/src/main/java/com/thealgorithms/conversions/OctalToBinary.java
index 6b01c2f65cfe..a66db97633b4 100644
--- a/src/main/java/com/thealgorithms/conversions/OctalToBinary.java
+++ b/src/main/java/com/thealgorithms/conversions/OctalToBinary.java
@@ -1,14 +1,40 @@
package com.thealgorithms.conversions;
/**
- * Converts any Octal Number to a Binary Number
+ * A utility class to convert an octal (base-8) number into its binary (base-2) representation.
+ *
+ * This class provides methods to:
+ *
+ * - Convert an octal number to its binary equivalent
+ * - Convert individual octal digits to binary
+ *
+ *
+ * Octal to Binary Conversion:
+ * An octal number is converted to binary by converting each octal digit to its 3-bit binary equivalent.
+ * The result is a long representing the full binary equivalent of the octal number.
+ *
+ * Example Usage
+ *
+ * long binary = OctalToBinary.convertOctalToBinary(52); // Output: 101010 (52 in octal is 101010 in binary)
+ *
*
* @author Bama Charan Chhandogi
+ * @see Octal Number System
+ * @see Binary Number System
*/
-
public final class OctalToBinary {
private OctalToBinary() {
}
+
+ /**
+ * Converts an octal number to its binary representation.
+ *
+ * Each octal digit is individually converted to its 3-bit binary equivalent, and the binary
+ * digits are concatenated to form the final binary number.
+ *
+ * @param octalNumber the octal number to convert (non-negative integer)
+ * @return the binary equivalent as a long
+ */
public static long convertOctalToBinary(int octalNumber) {
long binaryNumber = 0;
int digitPosition = 1;
@@ -20,12 +46,25 @@ public static long convertOctalToBinary(int octalNumber) {
binaryNumber += binaryDigit * digitPosition;
octalNumber /= 10;
- digitPosition *= 1000; // Move to the next group of 3 binary digits
+ digitPosition *= 1000;
}
return binaryNumber;
}
+ /**
+ * Converts a single octal digit (0-7) to its binary equivalent.
+ *
+ * For example:
+ *
+ * - Octal digit 7 is converted to binary 111
+ * - Octal digit 3 is converted to binary 011
+ *
+ *
+ *
+ * @param octalDigit a single octal digit (0-7)
+ * @return the binary equivalent as a long
+ */
public static long convertOctalDigitToBinary(int octalDigit) {
long binaryDigit = 0;
int binaryMultiplier = 1;
diff --git a/src/main/java/com/thealgorithms/conversions/OctalToDecimal.java b/src/main/java/com/thealgorithms/conversions/OctalToDecimal.java
index 187f0ed1e2ea..d91ce6eb3634 100644
--- a/src/main/java/com/thealgorithms/conversions/OctalToDecimal.java
+++ b/src/main/java/com/thealgorithms/conversions/OctalToDecimal.java
@@ -1,47 +1,42 @@
package com.thealgorithms.conversions;
-import java.util.Scanner;
-
/**
- * Converts any Octal Number to a Decimal Number
+ * Class for converting an octal number to a decimal number. Octal numbers are based on 8, using digits from 0 to 7.
*
- * @author Zachary Jones
*/
public final class OctalToDecimal {
+ private static final int OCTAL_BASE = 8;
+
private OctalToDecimal() {
}
/**
- * Main method
+ * Converts a given octal number (as a string) to its decimal representation.
+ * If the input is not a valid octal number (i.e., contains characters other than 0-7),
+ * the method throws an IllegalArgumentException.
*
- * @param args Command line arguments
+ * @param inputOctal The octal number as a string
+ * @return The decimal equivalent of the octal number
+ * @throws IllegalArgumentException if the input is not a valid octal number
*/
- public static void main(String[] args) {
- Scanner sc = new Scanner(System.in);
- System.out.print("Octal Input: ");
- String inputOctal = sc.nextLine();
- int result = convertOctalToDecimal(inputOctal);
- if (result != -1) {
- System.out.println("Result convertOctalToDecimal : " + result);
+ public static int convertOctalToDecimal(String inputOctal) {
+ if (inputOctal == null || inputOctal.isEmpty()) {
+ throw new IllegalArgumentException("Input cannot be null or empty");
}
- sc.close();
- }
- /**
- * This method converts an octal number to a decimal number.
- *
- * @param inputOctal The octal number
- * @return The decimal number
- */
- public static int convertOctalToDecimal(String inputOctal) {
- try {
- // Actual conversion of Octal to Decimal:
- return Integer.parseInt(inputOctal, 8);
- } catch (NumberFormatException ne) {
- // Printing a warning message if the input is not a valid octal
- // number:
- System.out.println("Invalid Input, Expecting octal number 0-7");
- return -1;
+ int decimalValue = 0;
+
+ for (int i = 0; i < inputOctal.length(); i++) {
+ char currentChar = inputOctal.charAt(i);
+
+ if (currentChar < '0' || currentChar > '7') {
+ throw new IllegalArgumentException("Incorrect input: Expecting an octal number (digits 0-7)");
+ }
+
+ int currentDigit = currentChar - '0';
+ decimalValue = decimalValue * OCTAL_BASE + currentDigit;
}
+
+ return decimalValue;
}
}
diff --git a/src/main/java/com/thealgorithms/conversions/OctalToHexadecimal.java b/src/main/java/com/thealgorithms/conversions/OctalToHexadecimal.java
index 5cc97fde12aa..bac56dc2e221 100644
--- a/src/main/java/com/thealgorithms/conversions/OctalToHexadecimal.java
+++ b/src/main/java/com/thealgorithms/conversions/OctalToHexadecimal.java
@@ -1,65 +1,61 @@
package com.thealgorithms.conversions;
-import java.util.Scanner;
-
/**
- * Converts any Octal Number to HexaDecimal
+ * Class for converting an Octal number to its Hexadecimal equivalent.
*
* @author Tanmay Joshi
*/
public final class OctalToHexadecimal {
+ private static final int OCTAL_BASE = 8;
+ private static final int HEX_BASE = 16;
+ private static final String HEX_DIGITS = "0123456789ABCDEF";
+
private OctalToHexadecimal() {
}
/**
- * This method converts a Octal number to a decimal number
+ * Converts an Octal number (as a string) to its Decimal equivalent.
*
- * @param s The Octal Number
- * @return The Decimal number
+ * @param octalNumber The Octal number as a string
+ * @return The Decimal equivalent of the Octal number
+ * @throws IllegalArgumentException if the input contains invalid octal digits
*/
- public static int octToDec(String s) {
- int i = 0;
- for (int j = 0; j < s.length(); j++) {
- char num = s.charAt(j);
- num -= '0';
- i *= 8;
- i += num;
+ public static int octalToDecimal(String octalNumber) {
+ if (octalNumber == null || octalNumber.isEmpty()) {
+ throw new IllegalArgumentException("Input cannot be null or empty");
}
- return i;
+
+ int decimalValue = 0;
+ for (int i = 0; i < octalNumber.length(); i++) {
+ char currentChar = octalNumber.charAt(i);
+ if (currentChar < '0' || currentChar > '7') {
+ throw new IllegalArgumentException("Incorrect octal digit: " + currentChar);
+ }
+ int currentDigit = currentChar - '0';
+ decimalValue = decimalValue * OCTAL_BASE + currentDigit;
+ }
+
+ return decimalValue;
}
/**
- * This method converts a Decimal number to a Hexadecimal number
+ * Converts a Decimal number to its Hexadecimal equivalent.
*
- * @param d The Decimal Number
- * @return The Hexadecimal number
+ * @param decimalNumber The Decimal number
+ * @return The Hexadecimal equivalent of the Decimal number
*/
- public static String decimalToHex(int d) {
- String digits = "0123456789ABCDEF";
- if (d <= 0) {
+ public static String decimalToHexadecimal(int decimalNumber) {
+ if (decimalNumber == 0) {
return "0";
}
- String hex = "";
- while (d > 0) {
- int digit = d % 16;
- hex = digits.charAt(digit) + hex;
- d = d / 16;
- }
- return hex;
- }
- public static void main(String[] args) {
- Scanner input = new Scanner(System.in);
- System.out.print("Enter the Octal number: ");
- // Take octal number as input from user in a string
- String oct = input.next();
-
- // Pass the octal number to function and get converted decimal form
- int decimal = octToDec(oct);
+ StringBuilder hexValue = new StringBuilder();
+ while (decimalNumber > 0) {
+ int digit = decimalNumber % HEX_BASE;
+ hexValue.insert(0, HEX_DIGITS.charAt(digit));
+ decimalNumber /= HEX_BASE;
+ }
- // Pass the decimal number to function and get converted Hex form of the number
- String hex = decimalToHex(decimal);
- System.out.println("The Hexadecimal equivalant is: " + hex);
- input.close();
+ return hexValue.toString();
}
}
diff --git a/src/main/java/com/thealgorithms/conversions/PhoneticAlphabetConverter.java b/src/main/java/com/thealgorithms/conversions/PhoneticAlphabetConverter.java
new file mode 100644
index 000000000000..730ce2214e2d
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/PhoneticAlphabetConverter.java
@@ -0,0 +1,84 @@
+package com.thealgorithms.conversions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Converts text to the NATO phonetic alphabet.
+ * Examples:
+ * "ABC" -> "Alpha Bravo Charlie"
+ * "Hello" -> "Hotel Echo Lima Lima Oscar"
+ * "123" -> "One Two Three"
+ * "A1B2C3" -> "Alpha One Bravo Two Charlie Three"
+ *
+ * @author Hardvan
+ */
+public final class PhoneticAlphabetConverter {
+ private PhoneticAlphabetConverter() {
+ }
+
+ private static final Map PHONETIC_MAP = new HashMap<>();
+
+ static {
+ PHONETIC_MAP.put('A', "Alpha");
+ PHONETIC_MAP.put('B', "Bravo");
+ PHONETIC_MAP.put('C', "Charlie");
+ PHONETIC_MAP.put('D', "Delta");
+ PHONETIC_MAP.put('E', "Echo");
+ PHONETIC_MAP.put('F', "Foxtrot");
+ PHONETIC_MAP.put('G', "Golf");
+ PHONETIC_MAP.put('H', "Hotel");
+ PHONETIC_MAP.put('I', "India");
+ PHONETIC_MAP.put('J', "Juliett");
+ PHONETIC_MAP.put('K', "Kilo");
+ PHONETIC_MAP.put('L', "Lima");
+ PHONETIC_MAP.put('M', "Mike");
+ PHONETIC_MAP.put('N', "November");
+ PHONETIC_MAP.put('O', "Oscar");
+ PHONETIC_MAP.put('P', "Papa");
+ PHONETIC_MAP.put('Q', "Quebec");
+ PHONETIC_MAP.put('R', "Romeo");
+ PHONETIC_MAP.put('S', "Sierra");
+ PHONETIC_MAP.put('T', "Tango");
+ PHONETIC_MAP.put('U', "Uniform");
+ PHONETIC_MAP.put('V', "Victor");
+ PHONETIC_MAP.put('W', "Whiskey");
+ PHONETIC_MAP.put('X', "X-ray");
+ PHONETIC_MAP.put('Y', "Yankee");
+ PHONETIC_MAP.put('Z', "Zulu");
+ PHONETIC_MAP.put('0', "Zero");
+ PHONETIC_MAP.put('1', "One");
+ PHONETIC_MAP.put('2', "Two");
+ PHONETIC_MAP.put('3', "Three");
+ PHONETIC_MAP.put('4', "Four");
+ PHONETIC_MAP.put('5', "Five");
+ PHONETIC_MAP.put('6', "Six");
+ PHONETIC_MAP.put('7', "Seven");
+ PHONETIC_MAP.put('8', "Eight");
+ PHONETIC_MAP.put('9', "Nine");
+ }
+
+ /**
+ * Converts text to the NATO phonetic alphabet.
+ * Steps:
+ * 1. Convert the text to uppercase.
+ * 2. Iterate over each character in the text.
+ * 3. Get the phonetic equivalent of the character from the map.
+ * 4. Append the phonetic equivalent to the result.
+ * 5. Append a space to separate the phonetic equivalents.
+ * 6. Return the result.
+ *
+ * @param text the text to convert
+ * @return the NATO phonetic alphabet
+ */
+ public static String textToPhonetic(String text) {
+ StringBuilder phonetic = new StringBuilder();
+ for (char c : text.toUpperCase().toCharArray()) {
+ if (Character.isWhitespace(c)) {
+ continue;
+ }
+ phonetic.append(PHONETIC_MAP.getOrDefault(c, String.valueOf(c))).append(" ");
+ }
+ return phonetic.toString().trim();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/conversions/RomanToInteger.java b/src/main/java/com/thealgorithms/conversions/RomanToInteger.java
index cf2d4145858f..a634c720326f 100644
--- a/src/main/java/com/thealgorithms/conversions/RomanToInteger.java
+++ b/src/main/java/com/thealgorithms/conversions/RomanToInteger.java
@@ -3,9 +3,27 @@
import java.util.HashMap;
import java.util.Map;
+/**
+ * A utility class to convert Roman numerals into integers.
+ *
+ * Roman numerals are based on seven symbols given below:
+ *
+ * - I = 1
+ * - V = 5
+ * - X = 10
+ * - L = 50
+ * - C = 100
+ * - D = 500
+ * - M = 1000
+ *
+ *
+ * If a smaller numeral appears before a larger numeral, it is subtracted.
+ * Otherwise, it is added. For example:
+ *
+ * MCMXCIV = 1000 + (1000 - 100) + (100 - 10) + (5 - 1) = 1994
+ *
+ */
public final class RomanToInteger {
- private RomanToInteger() {
- }
private static final Map ROMAN_TO_INT = new HashMap<>() {
{
@@ -19,47 +37,55 @@ private RomanToInteger() {
}
};
- // Roman Number = Roman Numerals
+ private RomanToInteger() {
+ }
/**
- * This function convert Roman number into Integer
+ * Converts a single Roman numeral character to its integer value.
*
- * @param a Roman number string
- * @return integer
+ * @param symbol the Roman numeral character
+ * @return the corresponding integer value
+ * @throws IllegalArgumentException if the symbol is not a valid Roman numeral
*/
- public static int romanToInt(String a) {
- a = a.toUpperCase();
- char prev = ' ';
-
- int sum = 0;
-
- int newPrev = 0;
- for (int i = a.length() - 1; i >= 0; i--) {
- char c = a.charAt(i);
-
- if (prev != ' ') {
- // checking current Number greater than previous or not
- newPrev = ROMAN_TO_INT.get(prev) > newPrev ? ROMAN_TO_INT.get(prev) : newPrev;
- }
+ private static int romanSymbolToInt(final char symbol) {
+ return ROMAN_TO_INT.computeIfAbsent(symbol, c -> { throw new IllegalArgumentException("Unknown Roman symbol: " + c); });
+ }
- int currentNum = ROMAN_TO_INT.get(c);
+ /**
+ * Converts a Roman numeral string to its integer equivalent.
+ * Steps:
+ *
+ * - Iterate over the string from right to left.
+ * - For each character, convert it to an integer value.
+ * - If the current value is greater than or equal to the max previous value, add it.
+ * - Otherwise, subtract it from the sum.
+ * - Update the max previous value.
+ * - Return the sum.
+ *
+ *
+ * @param roman the Roman numeral string
+ * @return the integer value of the Roman numeral
+ * @throws IllegalArgumentException if the input contains invalid Roman characters
+ * @throws NullPointerException if the input is {@code null}
+ */
+ public static int romanToInt(String roman) {
+ if (roman == null) {
+ throw new NullPointerException("Input cannot be null");
+ }
- // if current number greater than prev max previous then add
- if (currentNum >= newPrev) {
- sum += currentNum;
+ roman = roman.toUpperCase();
+ int sum = 0;
+ int maxPrevValue = 0;
+ for (int i = roman.length() - 1; i >= 0; i--) {
+ int currentValue = romanSymbolToInt(roman.charAt(i));
+ if (currentValue >= maxPrevValue) {
+ sum += currentValue;
+ maxPrevValue = currentValue;
} else {
- // subtract upcoming number until upcoming number not greater than prev max
- sum -= currentNum;
+ sum -= currentValue;
}
-
- prev = c;
}
return sum;
}
-
- public static void main(String[] args) {
- int sum = romanToInt("MDCCCIV");
- System.out.println(sum);
- }
}
diff --git a/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java b/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java
index 4d13b8b7fd55..30030de6c1bd 100644
--- a/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java
+++ b/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java
@@ -1,7 +1,5 @@
package com.thealgorithms.conversions;
-import java.util.Scanner;
-
/**
* Converts turkish character to latin character
*
@@ -11,21 +9,12 @@ public final class TurkishToLatinConversion {
private TurkishToLatinConversion() {
}
- /**
- * Main method
- *
- * @param args Command line arguments
- */
- public static void main(String[] args) {
- Scanner sc = new Scanner(System.in);
- System.out.println("Input the string: ");
- String b = sc.next();
- System.out.println("Converted: " + convertTurkishToLatin(b));
- sc.close();
- }
-
/**
* This method converts a turkish character to latin character.
+ * Steps:
+ * 1. Define turkish characters and their corresponding latin characters
+ * 2. Replace all turkish characters with their corresponding latin characters
+ * 3. Return the converted string
*
* @param param String paramter
* @return String
diff --git a/src/main/java/com/thealgorithms/conversions/UnitConversions.java b/src/main/java/com/thealgorithms/conversions/UnitConversions.java
new file mode 100644
index 000000000000..15f74a21a17e
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/UnitConversions.java
@@ -0,0 +1,51 @@
+package com.thealgorithms.conversions;
+
+import static java.util.Map.entry;
+
+import java.util.Map;
+import org.apache.commons.lang3.tuple.Pair;
+
+/**
+ * A utility class to perform unit conversions between different measurement systems.
+ *
+ * Currently, the class supports temperature conversions between several scales:
+ * Celsius, Fahrenheit, Kelvin, Réaumur, Delisle, and Rankine.
+ *
+ *
Example Usage
+ *
+ * double result = UnitConversions.TEMPERATURE.convert("Celsius", "Fahrenheit", 100.0);
+ * // Output: 212.0 (Celsius to Fahrenheit conversion of 100°C)
+ *
+ *
+ * This class makes use of an {@link UnitsConverter} that handles the conversion logic
+ * based on predefined affine transformations. These transformations include scaling factors
+ * and offsets for temperature conversions.
+ *
+ *
Temperature Scales Supported
+ *
+ * - Celsius
+ * - Fahrenheit
+ * - Kelvin
+ * - Réaumur
+ * - Delisle
+ * - Rankine
+ *
+ */
+public final class UnitConversions {
+ private UnitConversions() {
+ }
+
+ /**
+ * A preconfigured instance of {@link UnitsConverter} for temperature conversions.
+ * The converter handles conversions between the following temperature units:
+ *
+ * - Kelvin to Celsius
+ * - Celsius to Fahrenheit
+ * - Réaumur to Celsius
+ * - Delisle to Celsius
+ * - Rankine to Kelvin
+ *
+ */
+ public static final UnitsConverter TEMPERATURE = new UnitsConverter(Map.ofEntries(entry(Pair.of("Kelvin", "Celsius"), new AffineConverter(1.0, -273.15)), entry(Pair.of("Celsius", "Fahrenheit"), new AffineConverter(9.0 / 5.0, 32.0)),
+ entry(Pair.of("Réaumur", "Celsius"), new AffineConverter(5.0 / 4.0, 0.0)), entry(Pair.of("Delisle", "Celsius"), new AffineConverter(-2.0 / 3.0, 100.0)), entry(Pair.of("Rankine", "Kelvin"), new AffineConverter(5.0 / 9.0, 0.0))));
+}
diff --git a/src/main/java/com/thealgorithms/conversions/UnitsConverter.java b/src/main/java/com/thealgorithms/conversions/UnitsConverter.java
new file mode 100644
index 000000000000..00690b2c0f9b
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/UnitsConverter.java
@@ -0,0 +1,147 @@
+package com.thealgorithms.conversions;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import org.apache.commons.lang3.tuple.Pair;
+
+/**
+ * A class that handles unit conversions using affine transformations.
+ *
+ * The {@code UnitsConverter} allows converting values between different units using
+ * pre-defined affine conversion formulas. Each conversion is represented by an
+ * {@link AffineConverter} that defines the scaling and offset for the conversion.
+ *
+ *
For each unit, both direct conversions (e.g., Celsius to Fahrenheit) and inverse
+ * conversions (e.g., Fahrenheit to Celsius) are generated automatically. It also computes
+ * transitive conversions (e.g., Celsius to Kelvin via Fahrenheit if both conversions exist).
+ *
+ *
Key features include:
+ *
+ * - Automatic handling of inverse conversions (e.g., Fahrenheit to Celsius).
+ * - Compositional conversions, meaning if conversions between A -> B and B -> C exist,
+ * it can automatically generate A -> C conversion.
+ * - Supports multiple unit systems as long as conversions are provided in pairs.
+ *
+ *
+ * Example Usage
+ *
+ * Map<Pair<String, String>, AffineConverter> basicConversions = Map.ofEntries(
+ * entry(Pair.of("Celsius", "Fahrenheit"), new AffineConverter(9.0 / 5.0, 32.0)),
+ * entry(Pair.of("Kelvin", "Celsius"), new AffineConverter(1.0, -273.15))
+ * );
+ *
+ * UnitsConverter converter = new UnitsConverter(basicConversions);
+ * double result = converter.convert("Celsius", "Fahrenheit", 100.0);
+ * // Output: 212.0 (Celsius to Fahrenheit conversion of 100°C)
+ *
+ *
+ * Exception Handling
+ *
+ * - If the input unit and output unit are the same, an {@link IllegalArgumentException} is thrown.
+ * - If a conversion between the requested units does not exist, a {@link NoSuchElementException} is thrown.
+ *
+ */
+public final class UnitsConverter {
+ private final Map, AffineConverter> conversions;
+ private final Set units;
+
+ private static void putIfNeeded(Map, AffineConverter> conversions, final String inputUnit, final String outputUnit, final AffineConverter converter) {
+ if (!inputUnit.equals(outputUnit)) {
+ final var key = Pair.of(inputUnit, outputUnit);
+ conversions.putIfAbsent(key, converter);
+ }
+ }
+
+ private static Map, AffineConverter> addInversions(final Map, AffineConverter> knownConversions) {
+ Map, AffineConverter> res = new HashMap, AffineConverter>();
+ for (final var curConversion : knownConversions.entrySet()) {
+ final var inputUnit = curConversion.getKey().getKey();
+ final var outputUnit = curConversion.getKey().getValue();
+ putIfNeeded(res, inputUnit, outputUnit, curConversion.getValue());
+ putIfNeeded(res, outputUnit, inputUnit, curConversion.getValue().invert());
+ }
+ return res;
+ }
+
+ private static Map, AffineConverter> addCompositions(final Map, AffineConverter> knownConversions) {
+ Map, AffineConverter> res = new HashMap, AffineConverter>();
+ for (final var first : knownConversions.entrySet()) {
+ final var firstKey = first.getKey();
+ putIfNeeded(res, firstKey.getKey(), firstKey.getValue(), first.getValue());
+ for (final var second : knownConversions.entrySet()) {
+ final var secondKey = second.getKey();
+ if (firstKey.getValue().equals(secondKey.getKey())) {
+ final var newConversion = second.getValue().compose(first.getValue());
+ putIfNeeded(res, firstKey.getKey(), secondKey.getValue(), newConversion);
+ }
+ }
+ }
+ return res;
+ }
+
+ private static Map, AffineConverter> addAll(final Map, AffineConverter> knownConversions) {
+ final var res = addInversions(knownConversions);
+ return addCompositions(res);
+ }
+
+ private static Map, AffineConverter> computeAllConversions(final Map, AffineConverter> basicConversions) {
+ var tmp = basicConversions;
+ var res = addAll(tmp);
+ while (res.size() != tmp.size()) {
+ tmp = res;
+ res = addAll(tmp);
+ }
+ return res;
+ }
+
+ private static Set extractUnits(final Map, AffineConverter> conversions) {
+ Set res = new HashSet<>();
+ for (final var conversion : conversions.entrySet()) {
+ res.add(conversion.getKey().getKey());
+ }
+ return res;
+ }
+
+ /**
+ * Constructor for {@code UnitsConverter}.
+ *
+ * Accepts a map of basic conversions and automatically generates inverse and
+ * transitive conversions.
+ *
+ * @param basicConversions the initial set of unit conversions to add.
+ */
+ public UnitsConverter(final Map, AffineConverter> basicConversions) {
+ conversions = computeAllConversions(basicConversions);
+ units = extractUnits(conversions);
+ }
+
+ /**
+ * Converts a value from one unit to another.
+ *
+ * @param inputUnit the unit of the input value.
+ * @param outputUnit the unit to convert the value into.
+ * @param value the value to convert.
+ * @return the converted value in the target unit.
+ * @throws IllegalArgumentException if inputUnit equals outputUnit.
+ * @throws NoSuchElementException if no conversion exists between the units.
+ */
+ public double convert(final String inputUnit, final String outputUnit, final double value) {
+ if (inputUnit.equals(outputUnit)) {
+ throw new IllegalArgumentException("inputUnit must be different from outputUnit.");
+ }
+ final var conversionKey = Pair.of(inputUnit, outputUnit);
+ return conversions.computeIfAbsent(conversionKey, k -> { throw new NoSuchElementException("No converter for: " + k); }).convert(value);
+ }
+
+ /**
+ * Retrieves the set of all units supported by this converter.
+ *
+ * @return a set of available units.
+ */
+ public Set availableUnits() {
+ return units;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/conversions/WordsToNumber.java b/src/main/java/com/thealgorithms/conversions/WordsToNumber.java
new file mode 100644
index 000000000000..e2b81a0f4b47
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/WordsToNumber.java
@@ -0,0 +1,343 @@
+package com.thealgorithms.conversions;
+
+import java.io.Serial;
+import java.math.BigDecimal;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ A Java-based utility for converting English word representations of numbers
+ into their numeric form. This utility supports whole numbers, decimals,
+ large values up to trillions, and even scientific notation where applicable.
+ It ensures accurate parsing while handling edge cases like negative numbers,
+ improper word placements, and ambiguous inputs.
+ *
+ */
+
+public final class WordsToNumber {
+
+ private WordsToNumber() {
+ }
+
+ private enum NumberWord {
+ ZERO("zero", 0),
+ ONE("one", 1),
+ TWO("two", 2),
+ THREE("three", 3),
+ FOUR("four", 4),
+ FIVE("five", 5),
+ SIX("six", 6),
+ SEVEN("seven", 7),
+ EIGHT("eight", 8),
+ NINE("nine", 9),
+ TEN("ten", 10),
+ ELEVEN("eleven", 11),
+ TWELVE("twelve", 12),
+ THIRTEEN("thirteen", 13),
+ FOURTEEN("fourteen", 14),
+ FIFTEEN("fifteen", 15),
+ SIXTEEN("sixteen", 16),
+ SEVENTEEN("seventeen", 17),
+ EIGHTEEN("eighteen", 18),
+ NINETEEN("nineteen", 19),
+ TWENTY("twenty", 20),
+ THIRTY("thirty", 30),
+ FORTY("forty", 40),
+ FIFTY("fifty", 50),
+ SIXTY("sixty", 60),
+ SEVENTY("seventy", 70),
+ EIGHTY("eighty", 80),
+ NINETY("ninety", 90);
+
+ private final String word;
+ private final int value;
+
+ NumberWord(String word, int value) {
+ this.word = word;
+ this.value = value;
+ }
+
+ public static Integer getValue(String word) {
+ for (NumberWord num : values()) {
+ if (word.equals(num.word)) {
+ return num.value;
+ }
+ }
+ return null;
+ }
+ }
+
+ private enum PowerOfTen {
+ THOUSAND("thousand", new BigDecimal("1000")),
+ MILLION("million", new BigDecimal("1000000")),
+ BILLION("billion", new BigDecimal("1000000000")),
+ TRILLION("trillion", new BigDecimal("1000000000000"));
+
+ private final String word;
+ private final BigDecimal value;
+
+ PowerOfTen(String word, BigDecimal value) {
+ this.word = word;
+ this.value = value;
+ }
+
+ public static BigDecimal getValue(String word) {
+ for (PowerOfTen power : values()) {
+ if (word.equals(power.word)) {
+ return power.value;
+ }
+ }
+ return null;
+ }
+ }
+
+ public static String convert(String numberInWords) {
+ if (numberInWords == null) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.NULL_INPUT, "");
+ }
+
+ ArrayDeque wordDeque = preprocessWords(numberInWords);
+ BigDecimal completeNumber = convertWordQueueToBigDecimal(wordDeque);
+
+ return completeNumber.toString();
+ }
+
+ public static BigDecimal convertToBigDecimal(String numberInWords) {
+ String conversionResult = convert(numberInWords);
+ return new BigDecimal(conversionResult);
+ }
+
+ private static ArrayDeque preprocessWords(String numberInWords) {
+ String[] wordSplitArray = numberInWords.trim().split("[ ,-]");
+ ArrayDeque wordDeque = new ArrayDeque<>();
+ for (String word : wordSplitArray) {
+ if (word.isEmpty()) {
+ continue;
+ }
+ wordDeque.add(word.toLowerCase());
+ }
+ if (wordDeque.isEmpty()) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.NULL_INPUT, "");
+ }
+ return wordDeque;
+ }
+
+ private static void handleConjunction(boolean prevNumWasHundred, boolean prevNumWasPowerOfTen, ArrayDeque wordDeque) {
+ if (wordDeque.isEmpty()) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.INVALID_CONJUNCTION, "");
+ }
+
+ String nextWord = wordDeque.pollFirst();
+ String afterNextWord = wordDeque.peekFirst();
+
+ wordDeque.addFirst(nextWord);
+
+ Integer number = NumberWord.getValue(nextWord);
+
+ boolean isPrevWordValid = prevNumWasHundred || prevNumWasPowerOfTen;
+ boolean isNextWordValid = number != null && (number >= 10 || afterNextWord == null || "point".equals(afterNextWord));
+
+ if (!isPrevWordValid || !isNextWordValid) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.INVALID_CONJUNCTION, "");
+ }
+ }
+
+ private static BigDecimal handleHundred(BigDecimal currentChunk, String word, boolean prevNumWasPowerOfTen) {
+ boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0;
+ if (currentChunk.compareTo(BigDecimal.TEN) >= 0 || prevNumWasPowerOfTen) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word);
+ }
+ if (currentChunkIsZero) {
+ currentChunk = currentChunk.add(BigDecimal.ONE);
+ }
+ return currentChunk.multiply(BigDecimal.valueOf(100));
+ }
+
+ private static void handlePowerOfTen(List chunks, BigDecimal currentChunk, BigDecimal powerOfTen, String word, boolean prevNumWasPowerOfTen) {
+ boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0;
+ if (currentChunkIsZero || prevNumWasPowerOfTen) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word);
+ }
+ BigDecimal nextChunk = currentChunk.multiply(powerOfTen);
+
+ if (!(chunks.isEmpty() || isAdditionSafe(chunks.getLast(), nextChunk))) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word);
+ }
+ chunks.add(nextChunk);
+ }
+
+ private static BigDecimal handleNumber(Collection chunks, BigDecimal currentChunk, String word, Integer number) {
+ boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0;
+ if (number == 0 && !(currentChunkIsZero && chunks.isEmpty())) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word);
+ }
+ BigDecimal bigDecimalNumber = BigDecimal.valueOf(number);
+
+ if (!currentChunkIsZero && !isAdditionSafe(currentChunk, bigDecimalNumber)) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word);
+ }
+ return currentChunk.add(bigDecimalNumber);
+ }
+
+ private static void handlePoint(Collection chunks, BigDecimal currentChunk, ArrayDeque wordDeque) {
+ boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0;
+ if (!currentChunkIsZero) {
+ chunks.add(currentChunk);
+ }
+
+ String decimalPart = convertDecimalPart(wordDeque);
+ chunks.add(new BigDecimal(decimalPart));
+ }
+
+ private static void handleNegative(boolean isNegative) {
+ if (isNegative) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.MULTIPLE_NEGATIVES, "");
+ }
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.INVALID_NEGATIVE, "");
+ }
+
+ private static BigDecimal convertWordQueueToBigDecimal(ArrayDeque wordDeque) {
+ BigDecimal currentChunk = BigDecimal.ZERO;
+ List chunks = new ArrayList<>();
+
+ boolean isNegative = "negative".equals(wordDeque.peek());
+ if (isNegative) {
+ wordDeque.poll();
+ }
+
+ boolean prevNumWasHundred = false;
+ boolean prevNumWasPowerOfTen = false;
+
+ while (!wordDeque.isEmpty()) {
+ String word = wordDeque.poll();
+
+ switch (word) {
+ case "and" -> {
+ handleConjunction(prevNumWasHundred, prevNumWasPowerOfTen, wordDeque);
+ continue;
+ }
+ case "hundred" -> {
+ currentChunk = handleHundred(currentChunk, word, prevNumWasPowerOfTen);
+ prevNumWasHundred = true;
+ continue;
+ }
+ default -> {
+
+ }
+ }
+ prevNumWasHundred = false;
+
+ BigDecimal powerOfTen = PowerOfTen.getValue(word);
+ if (powerOfTen != null) {
+ handlePowerOfTen(chunks, currentChunk, powerOfTen, word, prevNumWasPowerOfTen);
+ currentChunk = BigDecimal.ZERO;
+ prevNumWasPowerOfTen = true;
+ continue;
+ }
+ prevNumWasPowerOfTen = false;
+
+ Integer number = NumberWord.getValue(word);
+ if (number != null) {
+ currentChunk = handleNumber(chunks, currentChunk, word, number);
+ continue;
+ }
+
+ switch (word) {
+ case "point" -> {
+ handlePoint(chunks, currentChunk, wordDeque);
+ currentChunk = BigDecimal.ZERO;
+ continue;
+ }
+ case "negative" -> {
+ handleNegative(isNegative);
+ }
+ default -> {
+
+ }
+ }
+
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.UNKNOWN_WORD, word);
+ }
+
+ if (currentChunk.compareTo(BigDecimal.ZERO) != 0) {
+ chunks.add(currentChunk);
+ }
+
+ BigDecimal completeNumber = combineChunks(chunks);
+ return isNegative ? completeNumber.multiply(BigDecimal.valueOf(-1))
+ :
+ completeNumber;
+ }
+
+ private static boolean isAdditionSafe(BigDecimal currentChunk, BigDecimal number) {
+ int chunkDigitCount = currentChunk.toString().length();
+ int numberDigitCount = number.toString().length();
+ return chunkDigitCount > numberDigitCount;
+ }
+
+ private static String convertDecimalPart(ArrayDeque wordDeque) {
+ StringBuilder decimalPart = new StringBuilder(".");
+
+ while (!wordDeque.isEmpty()) {
+ String word = wordDeque.poll();
+ Integer number = NumberWord.getValue(word);
+ if (number == null) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD_AFTER_POINT, word);
+ }
+ decimalPart.append(number);
+ }
+
+ boolean missingNumbers = decimalPart.length() == 1;
+ if (missingNumbers) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.MISSING_DECIMAL_NUMBERS, "");
+ }
+ return decimalPart.toString();
+ }
+
+ private static BigDecimal combineChunks(List chunks) {
+ BigDecimal completeNumber = BigDecimal.ZERO;
+ for (BigDecimal chunk : chunks) {
+ completeNumber = completeNumber.add(chunk);
+ }
+ return completeNumber;
+ }
+ }
+
+ class WordsToNumberException extends RuntimeException {
+
+ @Serial private static final long serialVersionUID = 1L;
+
+ enum ErrorType {
+ NULL_INPUT("'null' or empty input provided"),
+ UNKNOWN_WORD("Unknown Word: "),
+ UNEXPECTED_WORD("Unexpected Word: "),
+ UNEXPECTED_WORD_AFTER_POINT("Unexpected Word (after Point): "),
+ MISSING_DECIMAL_NUMBERS("Decimal part is missing numbers."),
+ MULTIPLE_NEGATIVES("Multiple 'Negative's detected."),
+ INVALID_NEGATIVE("Incorrect 'negative' placement"),
+ INVALID_CONJUNCTION("Incorrect 'and' placement");
+
+ private final String message;
+
+ ErrorType(String message) {
+ this.message = message;
+ }
+
+ public String formatMessage(String details) {
+ return "Invalid Input. " + message + (details.isEmpty() ? "" : details);
+ }
+ }
+
+ public final ErrorType errorType;
+
+ WordsToNumberException(ErrorType errorType, String details) {
+ super(errorType.formatMessage(details));
+ this.errorType = errorType;
+ }
+
+ public ErrorType getErrorType() {
+ return errorType;
+ }
+ }
diff --git a/src/main/java/com/thealgorithms/datastructures/bags/Bag.java b/src/main/java/com/thealgorithms/datastructures/bags/Bag.java
index ff5c832baeaf..afc3bbe40cce 100644
--- a/src/main/java/com/thealgorithms/datastructures/bags/Bag.java
+++ b/src/main/java/com/thealgorithms/datastructures/bags/Bag.java
@@ -4,23 +4,28 @@
import java.util.NoSuchElementException;
/**
- * Collection which does not allow removing elements (only collect and iterate)
+ * A generic collection that allows adding and iterating over elements but does not support
+ * element removal. This class implements a simple bag data structure, which can hold duplicate
+ * elements and provides operations to check for membership and the size of the collection.
*
- * @param - the generic type of an element in this bag
+ * Bag is not thread-safe and should not be accessed by multiple threads concurrently.
+ *
+ * @param the type of elements in this bag
*/
-public class Bag implements Iterable {
-
- private Node firstElement; // first element of the bag
- private int size; // size of bag
+public class Bag implements Iterable {
- private static final class Node {
+ private Node firstElement; // Reference to the first element in the bag
+ private int size; // Count of elements in the bag
- private Element content;
- private Node nextElement;
+ // Node class representing each element in the bag
+ private static final class Node {
+ private E content;
+ private Node nextElement;
}
/**
- * Create an empty bag
+ * Constructs an empty bag.
+ * This initializes the bag with zero elements.
*/
public Bag() {
firstElement = null;
@@ -28,38 +33,49 @@ public Bag() {
}
/**
- * @return true if this bag is empty, false otherwise
+ * Checks if the bag is empty.
+ *
+ * @return {@code true} if the bag contains no elements; {@code false} otherwise
*/
public boolean isEmpty() {
- return firstElement == null;
+ return size == 0;
}
/**
- * @return the number of elements
+ * Returns the number of elements in the bag.
+ *
+ * @return the number of elements currently in the bag
*/
public int size() {
return size;
}
/**
- * @param element - the element to add
+ * Adds an element to the bag.
+ *
+ *
This method adds the specified element to the bag. Duplicates are allowed, and the
+ * bag will maintain the order in which elements are added.
+ *
+ * @param element the element to add; must not be {@code null}
*/
- public void add(Element element) {
- Node oldfirst = firstElement;
- firstElement = new Node<>();
- firstElement.content = element;
- firstElement.nextElement = oldfirst;
+ public void add(E element) {
+ Node newNode = new Node<>();
+ newNode.content = element;
+ newNode.nextElement = firstElement;
+ firstElement = newNode;
size++;
}
/**
- * Checks if the bag contains a specific element
+ * Checks if the bag contains a specific element.
*
- * @param element which you want to look for
- * @return true if bag contains element, otherwise false
+ * This method uses the {@code equals} method of the element to determine membership.
+ *
+ * @param element the element to check for; must not be {@code null}
+ * @return {@code true} if the bag contains the specified element; {@code false} otherwise
*/
- public boolean contains(Element element) {
- for (Element value : this) {
+ public boolean contains(E element) {
+ for (E value : this) {
if (value.equals(element)) {
return true;
}
@@ -68,61 +84,55 @@ public boolean contains(Element element) {
}
/**
- * @return an iterator that iterates over the elements in this bag in
- * arbitrary order
+ * Returns an iterator over the elements in this bag.
+ *
+ *
The iterator provides a way to traverse the elements in the order they were added.
+ *
+ * @return an iterator that iterates over the elements in the bag
*/
- public Iterator iterator() {
+ @Override
+ public Iterator iterator() {
return new ListIterator<>(firstElement);
}
- @SuppressWarnings("hiding")
- private class ListIterator implements Iterator {
+ // Private class for iterating over elements
+ private static class ListIterator implements Iterator {
- private Node currentElement;
+ private Node currentElement;
- ListIterator(Node firstElement) {
- currentElement = firstElement;
+ /**
+ * Constructs a ListIterator starting from the given first element.
+ *
+ * @param firstElement the first element of the bag to iterate over
+ */
+ ListIterator(Node firstElement) {
+ this.currentElement = firstElement;
}
+ /**
+ * Checks if there are more elements to iterate over.
+ *
+ * @return {@code true} if there are more elements; {@code false} otherwise
+ */
+ @Override
public boolean hasNext() {
return currentElement != null;
}
/**
- * remove is not allowed in a bag
+ * Returns the next element in the iteration.
+ *
+ * @return the next element in the bag
+ * @throws NoSuchElementException if there are no more elements to return
*/
@Override
- public void remove() {
- throw new UnsupportedOperationException();
- }
-
- public Element next() {
+ public E next() {
if (!hasNext()) {
- throw new NoSuchElementException();
+ throw new NoSuchElementException("No more elements in the bag.");
}
- Element element = currentElement.content;
+ E element = currentElement.content;
currentElement = currentElement.nextElement;
return element;
}
}
-
- /**
- * main-method for testing
- */
- public static void main(String[] args) {
- Bag bag = new Bag<>();
-
- bag.add("1");
- bag.add("1");
- bag.add("2");
-
- System.out.println("size of bag = " + bag.size());
- for (String s : bag) {
- System.out.println(s);
- }
-
- System.out.println(bag.contains(null));
- System.out.println(bag.contains("1"));
- System.out.println(bag.contains("3"));
- }
}
diff --git a/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java b/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java
index a327690d7896..a2edd3db2d8e 100644
--- a/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java
+++ b/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java
@@ -2,35 +2,79 @@
import java.util.BitSet;
+/**
+ * A generic BloomFilter implementation for probabilistic membership checking.
+ *
+ * Bloom filters are space-efficient data structures that provide a fast way to test whether an
+ * element is a member of a set. They may produce false positives, indicating an element is
+ * in the set when it is not, but they will never produce false negatives.
+ *
+ *
+ * @param The type of elements to be stored in the Bloom filter.
+ */
public class BloomFilter {
- private int numberOfHashFunctions;
- private BitSet bitArray;
- private Hash[] hashFunctions;
+ private final int numberOfHashFunctions;
+ private final BitSet bitArray;
+ private final Hash[] hashFunctions;
- public BloomFilter(int numberOfHashFunctions, int n) {
+ /**
+ * Constructs a BloomFilter with a specified number of hash functions and bit array size.
+ *
+ * @param numberOfHashFunctions the number of hash functions to use
+ * @param bitArraySize the size of the bit array, which determines the capacity of the filter
+ * @throws IllegalArgumentException if numberOfHashFunctions or bitArraySize is less than 1
+ */
+ @SuppressWarnings("unchecked")
+ public BloomFilter(int numberOfHashFunctions, int bitArraySize) {
+ if (numberOfHashFunctions < 1 || bitArraySize < 1) {
+ throw new IllegalArgumentException("Number of hash functions and bit array size must be greater than 0");
+ }
this.numberOfHashFunctions = numberOfHashFunctions;
- hashFunctions = new Hash[numberOfHashFunctions];
- bitArray = new BitSet(n);
- insertHash();
+ this.bitArray = new BitSet(bitArraySize);
+ this.hashFunctions = new Hash[numberOfHashFunctions];
+ initializeHashFunctions();
}
- private void insertHash() {
+ /**
+ * Initializes the hash functions with unique indices to ensure different hashing.
+ */
+ private void initializeHashFunctions() {
for (int i = 0; i < numberOfHashFunctions; i++) {
- hashFunctions[i] = new Hash(i);
+ hashFunctions[i] = new Hash<>(i);
}
}
+ /**
+ * Inserts an element into the Bloom filter.
+ *
+ * This method hashes the element using all defined hash functions and sets the corresponding
+ * bits in the bit array.
+ *
+ *
+ * @param key the element to insert into the Bloom filter
+ */
public void insert(T key) {
for (Hash hash : hashFunctions) {
- int position = hash.compute(key) % bitArray.size();
+ int position = Math.abs(hash.compute(key) % bitArray.size());
bitArray.set(position);
}
}
+ /**
+ * Checks if an element might be in the Bloom filter.
+ *
+ * This method checks the bits at the positions computed by each hash function. If any of these
+ * bits are not set, the element is definitely not in the filter. If all bits are set, the element
+ * might be in the filter.
+ *
+ *
+ * @param key the element to check for membership in the Bloom filter
+ * @return {@code true} if the element might be in the Bloom filter, {@code false} if it is definitely not
+ */
public boolean contains(T key) {
for (Hash hash : hashFunctions) {
- int position = hash.compute(key) % bitArray.size();
+ int position = Math.abs(hash.compute(key) % bitArray.size());
if (!bitArray.get(position)) {
return false;
}
@@ -38,24 +82,57 @@ public boolean contains(T key) {
return true;
}
- private class Hash {
+ /**
+ * Inner class representing a hash function used by the Bloom filter.
+ *
+ * Each instance of this class represents a different hash function based on its index.
+ *
+ *
+ * @param The type of elements to be hashed.
+ */
+ private static class Hash {
- int index;
+ private final int index;
+ /**
+ * Constructs a Hash function with a specified index.
+ *
+ * @param index the index of this hash function, used to create a unique hash
+ */
Hash(int index) {
this.index = index;
}
+ /**
+ * Computes the hash of the given key.
+ *
+ * The hash value is calculated by multiplying the index of the hash function
+ * with the ASCII sum of the string representation of the key.
+ *
+ *
+ * @param key the element to hash
+ * @return the computed hash value
+ */
public int compute(T key) {
return index * asciiString(String.valueOf(key));
}
+ /**
+ * Computes the ASCII value sum of the characters in a string.
+ *
+ * This method iterates through each character of the string and accumulates
+ * their ASCII values to produce a single integer value.
+ *
+ *
+ * @param word the string to compute
+ * @return the sum of ASCII values of the characters in the string
+ */
private int asciiString(String word) {
- int number = 0;
- for (int i = 0; i < word.length(); i++) {
- number += word.charAt(i);
+ int sum = 0;
+ for (char c : word.toCharArray()) {
+ sum += c;
}
- return number;
+ return sum;
}
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java b/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java
index 15e9a0956226..b709e16fd1f6 100644
--- a/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java
+++ b/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java
@@ -2,27 +2,62 @@
import java.util.concurrent.atomic.AtomicInteger;
+/**
+ * The {@code CircularBuffer} class implements a generic circular (or ring) buffer.
+ * A circular buffer is a fixed-size data structure that operates in a FIFO (First In, First Out) manner.
+ * The buffer allows you to overwrite old data when the buffer is full and efficiently use limited memory.
+ * When the buffer is full, adding a new item will overwrite the oldest data.
+ *
+ * @param - The type of elements stored in the circular buffer.
+ */
public class CircularBuffer
- {
private final Item[] buffer;
private final CircularPointer putPointer;
private final CircularPointer getPointer;
private final AtomicInteger size = new AtomicInteger(0);
+ /**
+ * Constructor to initialize the circular buffer with a specified size.
+ *
+ * @param size The size of the circular buffer.
+ * @throws IllegalArgumentException if the size is zero or negative.
+ */
public CircularBuffer(int size) {
+ if (size <= 0) {
+ throw new IllegalArgumentException("Buffer size must be positive");
+ }
// noinspection unchecked
this.buffer = (Item[]) new Object[size];
this.putPointer = new CircularPointer(0, size);
this.getPointer = new CircularPointer(0, size);
}
+ /**
+ * Checks if the circular buffer is empty.
+ * This method is based on the current size of the buffer.
+ *
+ * @return {@code true} if the buffer is empty, {@code false} otherwise.
+ */
public boolean isEmpty() {
return size.get() == 0;
}
+ /**
+ * Checks if the circular buffer is full.
+ * The buffer is considered full when its size equals its capacity.
+ *
+ * @return {@code true} if the buffer is full, {@code false} otherwise.
+ */
public boolean isFull() {
return size.get() == buffer.length;
}
+ /**
+ * Retrieves and removes the item at the front of the buffer (FIFO).
+ * This operation will move the {@code getPointer} forward.
+ *
+ * @return The item at the front of the buffer, or {@code null} if the buffer is empty.
+ */
public Item get() {
if (isEmpty()) {
return null;
@@ -33,31 +68,64 @@ public Item get() {
return item;
}
+ /**
+ * Adds an item to the end of the buffer (FIFO).
+ * If the buffer is full, this operation will overwrite the oldest data.
+ *
+ * @param item The item to be added.
+ * @throws IllegalArgumentException if the item is null.
+ * @return {@code true} if the item was successfully added, {@code false} if the buffer was full and the item overwrote existing data.
+ */
public boolean put(Item item) {
+ if (item == null) {
+ throw new IllegalArgumentException("Null items are not allowed");
+ }
+
+ boolean wasEmpty = isEmpty();
if (isFull()) {
- return false;
+ getPointer.getAndIncrement(); // Move get pointer to discard oldest item
+ } else {
+ size.incrementAndGet();
}
buffer[putPointer.getAndIncrement()] = item;
- size.incrementAndGet();
- return true;
+ return wasEmpty;
}
+ /**
+ * The {@code CircularPointer} class is a helper class used to track the current index (pointer)
+ * in the circular buffer.
+ * The max value represents the capacity of the buffer.
+ * The `CircularPointer` class ensures that the pointer automatically wraps around to 0
+ * when it reaches the maximum index.
+ * This is achieved in the `getAndIncrement` method, where the pointer
+ * is incremented and then taken modulo the maximum value (`max`).
+ * This operation ensures that the pointer always stays within the bounds of the buffer.
+ */
private static class CircularPointer {
private int pointer;
private final int max;
+ /**
+ * Constructor to initialize the circular pointer.
+ *
+ * @param pointer The initial position of the pointer.
+ * @param max The maximum size (capacity) of the circular buffer.
+ */
CircularPointer(int pointer, int max) {
this.pointer = pointer;
this.max = max;
}
+ /**
+ * Increments the pointer by 1 and wraps it around to 0 if it reaches the maximum value.
+ * This ensures the pointer always stays within the buffer's bounds.
+ *
+ * @return The current pointer value before incrementing.
+ */
public int getAndIncrement() {
- if (pointer == max) {
- pointer = 0;
- }
int tmp = pointer;
- pointer++;
+ pointer = (pointer + 1) % max;
return tmp;
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java b/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java
index 6e37b4a7109d..f0d8ea8f7ff3 100644
--- a/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java
+++ b/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java
@@ -4,19 +4,46 @@
import java.util.Map;
/**
- * Java program for LFU Cache (https://en.wikipedia.org/wiki/Least_frequently_used)
+ * The {@code LFUCache} class implements a Least Frequently Used (LFU) cache.
+ * An LFU cache evicts the least frequently used item when the cache reaches its capacity.
+ * It maintains a mapping of keys to nodes, where each node contains the key, its associated value,
+ * and a frequency count that tracks how many times the item has been accessed. A doubly linked list
+ * is used to efficiently manage the ordering of items based on their usage frequency.
+ *
+ *
This implementation is designed to provide O(1) time complexity for both the {@code get} and
+ * {@code put} operations, which is achieved through the use of a hashmap for quick access and a
+ * doubly linked list for maintaining the order of item frequencies.
+ *
+ *
+ * Reference: LFU Cache - Wikipedia
+ *
+ *
+ * @param The type of keys maintained by this cache.
+ * @param The type of mapped values.
+ *
* @author Akshay Dubey (https://github.com/itsAkshayDubey)
*/
public class LFUCache {
+ /**
+ * The {@code Node} class represents an element in the LFU cache.
+ * Each node contains a key, a value, and a frequency count.
+ * It also has pointers to the previous and next nodes in the doubly linked list.
+ */
private class Node {
-
- private K key;
+ private final K key;
private V value;
private int frequency;
private Node previous;
private Node next;
+ /**
+ * Constructs a new {@code Node} with the specified key, value, and frequency.
+ *
+ * @param key The key associated with this node.
+ * @param value The value stored in this node.
+ * @param frequency The frequency of usage of this node.
+ */
Node(K key, V value, int frequency) {
this.key = key;
this.value = value;
@@ -26,67 +53,81 @@ private class Node {
private Node head;
private Node tail;
- private Map map = null;
- private Integer capacity;
+ private final Map cache;
+ private final int capacity;
private static final int DEFAULT_CAPACITY = 100;
+ /**
+ * Constructs an LFU cache with the default capacity.
+ */
public LFUCache() {
- this.capacity = DEFAULT_CAPACITY;
+ this(DEFAULT_CAPACITY);
}
- public LFUCache(Integer capacity) {
+ /**
+ * Constructs an LFU cache with the specified capacity.
+ *
+ * @param capacity The maximum number of items that the cache can hold.
+ * @throws IllegalArgumentException if the specified capacity is less than or equal to zero.
+ */
+ public LFUCache(int capacity) {
+ if (capacity <= 0) {
+ throw new IllegalArgumentException("Capacity must be greater than zero.");
+ }
this.capacity = capacity;
- this.map = new HashMap<>();
+ this.cache = new HashMap<>();
}
/**
- * This method returns value present in the cache corresponding to the key passed as parameter
+ * Retrieves the value associated with the given key from the cache.
+ * If the key exists, the node's frequency is incremented, and the node is repositioned
+ * in the linked list based on its updated frequency.
*
- * @param key for which value is to be retrieved
- * @returns object corresponding to the key passed as parameter, returns null if key is
- * not present in the cache
+ * @param key The key whose associated value is to be returned.
+ * @return The value associated with the key, or {@code null} if the key is not present in the cache.
*/
public V get(K key) {
- if (this.map.get(key) == null) {
+ Node node = cache.get(key);
+ if (node == null) {
return null;
}
-
- Node node = map.get(key);
removeNode(node);
node.frequency += 1;
addNodeWithUpdatedFrequency(node);
-
return node.value;
}
/**
- * This method stores key and value in the cache
+ * Inserts or updates a key-value pair in the cache.
+ * If the key already exists, the value is updated and its frequency is incremented.
+ * If the cache is full, the least frequently used item is removed before inserting the new item.
*
- * @param key which is to be stored in the cache
- * @param value which is to be stored in the cache
+ * @param key The key associated with the value to be inserted or updated.
+ * @param value The value to be inserted or updated.
*/
public void put(K key, V value) {
- if (map.containsKey(key)) {
- Node node = map.get(key);
+ if (cache.containsKey(key)) {
+ Node node = cache.get(key);
node.value = value;
node.frequency += 1;
removeNode(node);
addNodeWithUpdatedFrequency(node);
} else {
- if (map.size() >= capacity) {
- map.remove(this.head.key);
+ if (cache.size() >= capacity) {
+ cache.remove(this.head.key); // Evict least frequently used item
removeNode(head);
}
Node node = new Node(key, value, 1);
addNodeWithUpdatedFrequency(node);
- map.put(key, node);
+ cache.put(key, node);
}
}
/**
- * This method stores the node in the cache with updated frequency
+ * Adds a node to the linked list in the correct position based on its frequency.
+ * The linked list is ordered by frequency, with the least frequently used node at the head.
*
- * @param Node node which is to be updated in the cache
+ * @param node The node to be inserted into the list.
*/
private void addNodeWithUpdatedFrequency(Node node) {
if (tail != null && head != null) {
@@ -123,9 +164,10 @@ private void addNodeWithUpdatedFrequency(Node node) {
}
/**
- * This method removes node from the cache
+ * Removes a node from the doubly linked list.
+ * This method ensures that the pointers of neighboring nodes are properly updated.
*
- * @param Node node which is to be removed in the cache
+ * @param node The node to be removed from the list.
*/
private void removeNode(Node node) {
if (node.previous != null) {
diff --git a/src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java b/src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java
index 97818ff83351..ec39d2a6ed28 100644
--- a/src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java
+++ b/src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java
@@ -4,15 +4,40 @@
import java.util.Map;
/**
- * Least recently used (LRU)
- *
- * Discards the least recently used items first. This algorithm requires keeping
- * track of what was used when, which is expensive if one wants to make sure the
- * algorithm always discards the least recently used item.
- * https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)
+ * A Least Recently Used (LRU) Cache implementation.
*
- * @param key type
- * @param value type
+ * An LRU cache is a fixed-size cache that maintains items in order of use. When the cache reaches
+ * its capacity and a new item needs to be added, it removes the least recently used item first.
+ * This implementation provides O(1) time complexity for both get and put operations.
+ *
+ * Features:
+ *
+ * - Fixed-size cache with configurable capacity
+ * - Constant time O(1) operations for get and put
+ * - Thread-unsafe - should be externally synchronized if used in concurrent environments
+ * - Supports null values but not null keys
+ *
+ *
+ * Implementation Details:
+ *
+ * - Uses a HashMap for O(1) key-value lookups
+ * - Maintains a doubly-linked list for tracking access order
+ * - The head of the list contains the least recently used item
+ * - The tail of the list contains the most recently used item
+ *
+ *
+ * Example usage:
+ *
+ * LRUCache cache = new LRUCache<>(3); // Create cache with capacity 3
+ * cache.put("A", 1); // Cache: A=1
+ * cache.put("B", 2); // Cache: A=1, B=2
+ * cache.put("C", 3); // Cache: A=1, B=2, C=3
+ * cache.get("A"); // Cache: B=2, C=3, A=1 (A moved to end)
+ * cache.put("D", 4); // Cache: C=3, A=1, D=4 (B evicted)
+ *
+ *
+ * @param the type of keys maintained by this cache
+ * @param the type of mapped values
*/
public class LRUCache {
@@ -30,6 +55,11 @@ public LRUCache(int cap) {
setCapacity(cap);
}
+ /**
+ * Returns the current capacity of the cache.
+ *
+ * @param newCapacity the new capacity of the cache
+ */
private void setCapacity(int newCapacity) {
checkCapacity(newCapacity);
for (int i = data.size(); i > newCapacity; i--) {
@@ -39,6 +69,11 @@ private void setCapacity(int newCapacity) {
this.cap = newCapacity;
}
+ /**
+ * Evicts the least recently used item from the cache.
+ *
+ * @return the evicted entry
+ */
private Entry evict() {
if (head == null) {
throw new RuntimeException("cache cannot be empty!");
@@ -50,12 +85,25 @@ private Entry evict() {
return evicted;
}
+ /**
+ * Checks if the capacity is valid.
+ *
+ * @param capacity the capacity to check
+ */
private void checkCapacity(int capacity) {
if (capacity <= 0) {
throw new RuntimeException("capacity must greater than 0!");
}
}
+ /**
+ * Returns the value to which the specified key is mapped, or null if this cache contains no
+ * mapping for the key.
+ *
+ * @param key the key whose associated value is to be returned
+ * @return the value to which the specified key is mapped, or null if this cache contains no
+ * mapping for the key
+ */
public V get(K key) {
if (!data.containsKey(key)) {
return null;
@@ -65,6 +113,11 @@ public V get(K key) {
return entry.getValue();
}
+ /**
+ * Moves the specified entry to the end of the list.
+ *
+ * @param entry the entry to move
+ */
private void moveNodeToLast(Entry entry) {
if (tail == entry) {
return;
@@ -86,6 +139,12 @@ private void moveNodeToLast(Entry entry) {
tail = entry;
}
+ /**
+ * Associates the specified value with the specified key in this cache.
+ *
+ * @param key the key with which the specified value is to be associated
+ * @param value the value to be associated with the specified key
+ */
public void put(K key, V value) {
if (data.containsKey(key)) {
final Entry existingEntry = data.get(key);
@@ -107,6 +166,11 @@ public void put(K key, V value) {
data.put(key, newEntry);
}
+ /**
+ * Adds a new entry to the end of the list.
+ *
+ * @param newEntry the entry to add
+ */
private void addNewEntry(Entry newEntry) {
if (data.isEmpty()) {
head = newEntry;
diff --git a/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java b/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java
index 9c155be8b195..93b13e6ad654 100644
--- a/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java
+++ b/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java
@@ -4,14 +4,17 @@
import java.util.Map;
/**
- * Most recently used (MRU)
+ * Represents a Most Recently Used (MRU) Cache.
*
- * In contrast to Least Recently Used (LRU), MRU discards the most recently used
- * items first.
- * https://en.wikipedia.org/wiki/Cache_replacement_policies#Most_recently_used_(MRU)
+ * In contrast to the Least Recently Used (LRU) strategy, the MRU caching policy
+ * evicts the most recently accessed items first. This class provides methods to
+ * store key-value pairs and manage cache eviction based on this policy.
*
- * @param key type
- * @param value type
+ * For more information, refer to:
+ * MRU on Wikipedia.
+ *
+ * @param the type of keys maintained by this cache
+ * @param the type of values associated with the keys
*/
public class MRUCache {
@@ -21,40 +24,74 @@ public class MRUCache {
private int cap;
private static final int DEFAULT_CAP = 100;
+ /**
+ * Creates an MRUCache with the default capacity.
+ */
public MRUCache() {
setCapacity(DEFAULT_CAP);
}
+ /**
+ * Creates an MRUCache with a specified capacity.
+ *
+ * @param cap the maximum number of items the cache can hold
+ */
+ public MRUCache(int cap) {
+ setCapacity(cap);
+ }
+
+ /**
+ * Sets the capacity of the cache and evicts items if the new capacity
+ * is less than the current number of items.
+ *
+ * @param newCapacity the new capacity to set
+ */
private void setCapacity(int newCapacity) {
checkCapacity(newCapacity);
- for (int i = data.size(); i > newCapacity; i--) {
+ while (data.size() > newCapacity) {
Entry evicted = evict();
data.remove(evicted.getKey());
}
this.cap = newCapacity;
}
+ /**
+ * Checks if the specified capacity is valid.
+ *
+ * @param capacity the capacity to check
+ * @throws IllegalArgumentException if the capacity is less than or equal to zero
+ */
private void checkCapacity(int capacity) {
if (capacity <= 0) {
- throw new RuntimeException("capacity must greater than 0!");
+ throw new IllegalArgumentException("Capacity must be greater than 0!");
}
}
+ /**
+ * Evicts the most recently used entry from the cache.
+ *
+ * @return the evicted entry
+ * @throws RuntimeException if the cache is empty
+ */
private Entry evict() {
if (head == null) {
- throw new RuntimeException("cache cannot be empty!");
+ throw new RuntimeException("Cache cannot be empty!");
}
final Entry evicted = this.tail;
tail = evicted.getPreEntry();
- tail.setNextEntry(null);
+ if (tail != null) {
+ tail.setNextEntry(null);
+ }
evicted.setNextEntry(null);
return evicted;
}
- public MRUCache(int cap) {
- setCapacity(cap);
- }
-
+ /**
+ * Retrieves the value associated with the specified key.
+ *
+ * @param key the key whose associated value is to be returned
+ * @return the value associated with the specified key, or null if the key does not exist
+ */
public V get(K key) {
if (!data.containsKey(key)) {
return null;
@@ -64,11 +101,19 @@ public V get(K key) {
return entry.getValue();
}
+ /**
+ * Associates the specified value with the specified key in the cache.
+ * If the key already exists, its value is updated and the entry is moved to the most recently used position.
+ * If the cache is full, the most recently used entry is evicted before adding the new entry.
+ *
+ * @param key the key with which the specified value is to be associated
+ * @param value the value to be associated with the specified key
+ */
public void put(K key, V value) {
if (data.containsKey(key)) {
- final Entry exitingEntry = data.get(key);
- exitingEntry.setValue(value);
- moveEntryToLast(exitingEntry);
+ final Entry existingEntry = data.get(key);
+ existingEntry.setValue(value);
+ moveEntryToLast(existingEntry);
return;
}
Entry newEntry;
@@ -84,6 +129,11 @@ public void put(K key, V value) {
data.put(key, newEntry);
}
+ /**
+ * Adds a new entry to the cache and updates the head and tail pointers accordingly.
+ *
+ * @param newEntry the new entry to be added
+ */
private void addNewEntry(Entry newEntry) {
if (data.isEmpty()) {
head = newEntry;
@@ -96,6 +146,11 @@ private void addNewEntry(Entry newEntry) {
tail = newEntry;
}
+ /**
+ * Moves the specified entry to the most recently used position in the cache.
+ *
+ * @param entry the entry to be moved
+ */
private void moveEntryToLast(Entry entry) {
if (tail == entry) {
return;
@@ -117,8 +172,14 @@ private void moveEntryToLast(Entry entry) {
tail = entry;
}
+ /**
+ * A nested class representing an entry in the cache, which holds a key-value pair
+ * and references to the previous and next entries in the linked list structure.
+ *
+ * @param the type of the key
+ * @param the type of the value
+ */
static final class Entry {
-
private Entry preEntry;
private Entry nextEntry;
private I key;
diff --git a/src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java b/src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java
index 2c6ce8a427d1..d33bd3ee84d9 100644
--- a/src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java
+++ b/src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java
@@ -1,53 +1,33 @@
package com.thealgorithms.datastructures.crdt;
+import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
/**
- * Last-Write-Wins Element Set (LWWElementSet) is a state-based CRDT (Conflict-free Replicated Data Type)
- * designed for managing sets in a distributed and concurrent environment. It supports the addition and removal
- * of elements, using timestamps to determine the order of operations. The set is split into two subsets:
- * the add set for elements to be added and the remove set for elements to be removed.
+ * Last-Write-Wins Element Set (LWWElementSet) is a state-based CRDT (Conflict-free Replicated Data
+ * Type) designed for managing sets in a distributed and concurrent environment. It supports the
+ * addition and removal of elements, using timestamps to determine the order of operations. The set
+ * is split into two subsets: the add set for elements to be added and the remove set for elements
+ * to be removed. The LWWElementSet ensures that the most recent operation (based on the timestamp)
+ * wins in the case of concurrent operations.
*
- * @author itakurah (Niklas Hoefflin) (https://github.com/itakurah)
- * @see Conflict-free_replicated_data_type
- * @see itakurah (Niklas Hoefflin)
+ * @param The type of the elements in the LWWElementSet.
+ * @author itakurah (GitHub), Niklas Hoefflin (LinkedIn)
+ * @see Conflict free
+ * replicated data type (Wikipedia)
+ * @see A comprehensive study of
+ * Convergent and Commutative Replicated Data Types
*/
-
-class Element {
- String key;
- int timestamp;
- Bias bias;
+class LWWElementSet {
+ final Map> addSet;
+ final Map> removeSet;
/**
- * Constructs a new Element with the specified key, timestamp and bias.
- *
- * @param key The key of the element.
- * @param timestamp The timestamp associated with the element.
- * @param bias The bias of the element (ADDS or REMOVALS).
- */
- Element(String key, int timestamp, Bias bias) {
- this.key = key;
- this.timestamp = timestamp;
- this.bias = bias;
- }
-}
-
-enum Bias {
- /**
- * ADDS bias for the add set.
- * REMOVALS bias for the remove set.
- */
- ADDS,
- REMOVALS
-}
-
-class LWWElementSet {
- private final Map addSet;
- private final Map removeSet;
-
- /**
- * Constructs an empty LWWElementSet.
+ * Constructs an empty LWWElementSet. This constructor initializes the addSet and removeSet as
+ * empty HashMaps. The addSet stores elements that are added, and the removeSet stores elements
+ * that are removed.
*/
LWWElementSet() {
this.addSet = new HashMap<>();
@@ -55,84 +35,92 @@ class LWWElementSet {
}
/**
- * Adds an element to the addSet.
+ * Adds an element to the addSet with the current timestamp. This method stores the element in the
+ * addSet, ensuring that the element is added to the set with an associated timestamp that
+ * represents the time of the addition.
*
- * @param e The element to be added.
+ * @param key The key of the element to be added.
*/
- public void add(Element e) {
- addSet.put(e.key, e);
+ public void add(T key) {
+ addSet.put(key, new Element<>(key, Instant.now()));
}
/**
- * Removes an element from the removeSet.
+ * Removes an element by adding it to the removeSet with the current timestamp. This method adds
+ * the element to the removeSet, marking it as removed with the current timestamp.
*
- * @param e The element to be removed.
+ * @param key The key of the element to be removed.
*/
- public void remove(Element e) {
- if (lookup(e)) {
- removeSet.put(e.key, e);
- }
+ public void remove(T key) {
+ removeSet.put(key, new Element<>(key, Instant.now()));
}
/**
- * Checks if an element is in the LWWElementSet by comparing timestamps in the addSet and removeSet.
+ * Checks if an element is in the LWWElementSet. An element is considered present if it exists in
+ * the addSet and either does not exist in the removeSet, or its add timestamp is later than any
+ * corresponding remove timestamp.
*
- * @param e The element to be checked.
- * @return True if the element is present, false otherwise.
+ * @param key The key of the element to be checked.
+ * @return {@code true} if the element is present in the set (i.e., its add timestamp is later
+ * than its remove timestamp, or it is not in the remove set), {@code false} otherwise (i.e.,
+ * the element has been removed or its remove timestamp is later than its add timestamp).
*/
- public boolean lookup(Element e) {
- Element inAddSet = addSet.get(e.key);
- Element inRemoveSet = removeSet.get(e.key);
+ public boolean lookup(T key) {
+ Element inAddSet = addSet.get(key);
+ Element inRemoveSet = removeSet.get(key);
- return (inAddSet != null && (inRemoveSet == null || inAddSet.timestamp > inRemoveSet.timestamp));
+ return inAddSet != null && (inRemoveSet == null || inAddSet.timestamp.isAfter(inRemoveSet.timestamp));
}
/**
- * Compares the LWWElementSet with another LWWElementSet to check if addSet and removeSet are a subset.
+ * Merges another LWWElementSet into this set. This method takes the union of both the add-sets
+ * and remove-sets from the two sets, resolving conflicts by keeping the element with the latest
+ * timestamp. If an element appears in both the add-set and remove-set of both sets, the one with
+ * the later timestamp will be retained.
*
- * @param other The LWWElementSet to compare.
- * @return True if the set is subset, false otherwise.
+ * @param other The LWWElementSet to merge with the current set.
*/
- public boolean compare(LWWElementSet other) {
- return other.addSet.keySet().containsAll(addSet.keySet()) && other.removeSet.keySet().containsAll(removeSet.keySet());
+ public void merge(LWWElementSet other) {
+ for (Map.Entry> entry : other.addSet.entrySet()) {
+ addSet.merge(entry.getKey(), entry.getValue(), this::resolveConflict);
+ }
+ for (Map.Entry> entry : other.removeSet.entrySet()) {
+ removeSet.merge(entry.getKey(), entry.getValue(), this::resolveConflict);
+ }
}
/**
- * Merges another LWWElementSet into this set by resolving conflicts based on timestamps.
+ * Resolves conflicts between two elements by selecting the one with the later timestamp. This
+ * method is used when merging two LWWElementSets to ensure that the most recent operation (based
+ * on timestamps) is kept.
*
- * @param other The LWWElementSet to merge.
+ * @param e1 The first element.
+ * @param e2 The second element.
+ * @return The element with the later timestamp.
*/
- public void merge(LWWElementSet other) {
- for (Element e : other.addSet.values()) {
- if (!addSet.containsKey(e.key) || compareTimestamps(addSet.get(e.key), e)) {
- addSet.put(e.key, e);
- }
- }
-
- for (Element e : other.removeSet.values()) {
- if (!removeSet.containsKey(e.key) || compareTimestamps(removeSet.get(e.key), e)) {
- removeSet.put(e.key, e);
- }
- }
+ private Element resolveConflict(Element e1, Element e2) {
+ return e1.timestamp.isAfter(e2.timestamp) ? e1 : e2;
}
+}
+
+/**
+ * Represents an element in the LWWElementSet, consisting of a key and a timestamp. This class is
+ * used to store the elements in both the add and remove sets with their respective timestamps.
+ *
+ * @param The type of the key associated with the element.
+ */
+class Element {
+ T key;
+ Instant timestamp;
/**
- * Compares timestamps of two elements based on their bias (ADDS or REMOVALS).
+ * Constructs a new Element with the specified key and timestamp.
*
- * @param e The first element.
- * @param other The second element.
- * @return True if the first element's timestamp is greater or the bias is ADDS and timestamps are equal.
+ * @param key The key of the element.
+ * @param timestamp The timestamp associated with the element.
*/
- public boolean compareTimestamps(Element e, Element other) {
- if (e.bias != other.bias) {
- throw new IllegalArgumentException("Invalid bias value");
- }
- Bias bias = e.bias;
- int timestampComparison = Integer.compare(e.timestamp, other.timestamp);
-
- if (timestampComparison == 0) {
- return bias != Bias.ADDS;
- }
- return timestampComparison < 0;
+ Element(T key, Instant timestamp) {
+ this.key = key;
+ this.timestamp = timestamp;
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java b/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java
index cfec2e3b2c37..cd5dc580b694 100644
--- a/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java
+++ b/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java
@@ -10,218 +10,258 @@
import java.util.stream.StreamSupport;
/**
- * This class implements a dynamic array
+ * This class implements a dynamic array, which can grow or shrink in size
+ * as elements are added or removed. It provides an array-like interface
+ * with methods to add, remove, and access elements, along with iterators
+ * to traverse the elements.
*
- * @param the type that each index of the array will hold
+ * @param the type of elements that this array can hold
*/
public class DynamicArray implements Iterable {
private static final int DEFAULT_CAPACITY = 16;
-
- private int capacity;
private int size;
+ private int modCount; // Tracks structural modifications for iterator integrity
private Object[] elements;
/**
- * constructor
+ * Constructs a new DynamicArray with the specified initial capacity.
*
- * @param capacity the starting length of the desired array
+ * @param capacity the initial capacity of the array
+ * @throws IllegalArgumentException if the specified capacity is negative
*/
public DynamicArray(final int capacity) {
+ if (capacity < 0) {
+ throw new IllegalArgumentException("Capacity cannot be negative.");
+ }
this.size = 0;
- this.capacity = capacity;
- this.elements = new Object[this.capacity];
+ this.modCount = 0;
+ this.elements = new Object[capacity];
}
/**
- * No-args constructor
+ * Constructs a new DynamicArray with a default initial capacity.
*/
public DynamicArray() {
this(DEFAULT_CAPACITY);
}
/**
- * Adds an element to the array If full, creates a copy array twice the size
- * of the current one
+ * Adds an element to the end of the array. If the array is full, it
+ * creates a new array with double the size to accommodate the new element.
*
- * @param element the element of type to be added to the array
+ * @param element the element to be added to the array
*/
public void add(final E element) {
- if (this.size == this.elements.length) {
- this.elements = Arrays.copyOf(this.elements, newCapacity(2 * this.capacity));
- }
-
- this.elements[this.size] = element;
- size++;
+ ensureCapacity(size + 1);
+ elements[size++] = element;
+ modCount++; // Increment modification count
}
/**
- * Places element of type at the desired index
+ * Places an element at the specified index, expanding capacity if necessary.
*
- * @param index the index for the element to be placed
- * @param element the element to be inserted
+ * @param index the index at which the element is to be placed
+ * @param element the element to be inserted at the specified index
+ * @throws IndexOutOfBoundsException if index is less than 0 or greater than or equal to the number of elements
*/
public void put(final int index, E element) {
- this.elements[index] = element;
+ if (index < 0) {
+ throw new IndexOutOfBoundsException("Index cannot be negative.");
+ }
+ ensureCapacity(index + 1);
+ elements[index] = element;
+ if (index >= size) {
+ size = index + 1;
+ }
+ modCount++; // Increment modification count
}
/**
- * get method for element at a given index returns null if the index is
- * empty
+ * Retrieves the element at the specified index.
*
- * @param index the desired index of the element
- * @return the element at the specified index
+ * @param index the index of the element to retrieve
+ * @return the element at the specified index
+ * @throws IndexOutOfBoundsException if index is less than 0 or greater than or equal to the current size
*/
+ @SuppressWarnings("unchecked")
public E get(final int index) {
- return getElement(index);
+ if (index < 0 || index >= size) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
+ }
+ return (E) elements[index];
}
/**
- * Removes an element from the array
+ * Removes and returns the element at the specified index.
*
* @param index the index of the element to be removed
- * @return the element removed
+ * @return the element that was removed from the array
+ * @throws IndexOutOfBoundsException if index is less than 0 or greater than or equal to the current size
*/
public E remove(final int index) {
- final E oldElement = getElement(index);
- fastRemove(this.elements, index);
-
- if (this.capacity > DEFAULT_CAPACITY && size * 4 <= this.capacity) {
- this.elements = Arrays.copyOf(this.elements, newCapacity(this.capacity / 2));
+ if (index < 0 || index >= size) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
+ @SuppressWarnings("unchecked") E oldElement = (E) elements[index];
+ fastRemove(index);
+ modCount++; // Increment modification count
return oldElement;
}
/**
- * get method for size field
+ * Returns the current number of elements in the array.
*
- * @return int size
+ * @return the number of elements in the array
*/
public int getSize() {
- return this.size;
+ return size;
}
/**
- * isEmpty helper method
+ * Checks whether the array is empty.
*
- * @return boolean true if the array contains no elements, false otherwise
+ * @return true if the array contains no elements, false otherwise
*/
public boolean isEmpty() {
- return this.size == 0;
+ return size == 0;
}
+ /**
+ * Returns a sequential stream with this collection as its source.
+ *
+ * @return a stream of the elements in the array
+ */
public Stream stream() {
return StreamSupport.stream(spliterator(), false);
}
- private void fastRemove(final Object[] elements, final int index) {
- final int newSize = this.size - 1;
-
- if (newSize > index) {
- System.arraycopy(elements, index + 1, elements, index, newSize - index);
+ /**
+ * Ensures that the array has enough capacity to hold the specified number of elements.
+ *
+ * @param minCapacity the minimum capacity required
+ */
+ private void ensureCapacity(int minCapacity) {
+ if (minCapacity > elements.length) {
+ int newCapacity = Math.max(elements.length * 2, minCapacity);
+ elements = Arrays.copyOf(elements, newCapacity);
}
-
- this.size = newSize;
- this.elements[this.size] = null;
- }
-
- private E getElement(final int index) {
- return (E) this.elements[index];
}
- private int newCapacity(int capacity) {
- this.capacity = capacity;
- return this.capacity;
+ /**
+ * Removes the element at the specified index without resizing the array.
+ * This method shifts any subsequent elements to the left and clears the last element.
+ *
+ * @param index the index of the element to remove
+ */
+ private void fastRemove(int index) {
+ int numMoved = size - index - 1;
+ if (numMoved > 0) {
+ System.arraycopy(elements, index + 1, elements, index, numMoved);
+ }
+ elements[--size] = null; // Clear to let GC do its work
}
/**
- * returns a String representation of this object
+ * Returns a string representation of the array, including only the elements that are currently stored.
*
- * @return String a String representing the array
+ * @return a string containing the elements in the array
*/
@Override
public String toString() {
- return Arrays.toString(Arrays.stream(this.elements).filter(Objects::nonNull).toArray());
+ return Arrays.toString(Arrays.copyOf(elements, size));
}
/**
- * Creates and returns a new Dynamic Array Iterator
+ * Returns an iterator over the elements in this array in proper sequence.
*
- * @return Iterator a Dynamic Array Iterator
+ * @return an Iterator over the elements in the array
*/
@Override
public Iterator iterator() {
return new DynamicArrayIterator();
}
+ /**
+ * Private iterator class for the DynamicArray.
+ */
private final class DynamicArrayIterator implements Iterator {
private int cursor;
+ private int expectedModCount;
+ /**
+ * Constructs a new iterator for the dynamic array.
+ */
+ DynamicArrayIterator() {
+ this.expectedModCount = modCount;
+ }
+
+ /**
+ * Checks if there are more elements in the iteration.
+ *
+ * @return true if there are more elements, false otherwise
+ */
@Override
public boolean hasNext() {
- return this.cursor != size;
+ checkForComodification();
+ return cursor < size;
}
+ /**
+ * Returns the next element in the iteration.
+ *
+ * @return the next element in the iteration
+ * @throws NoSuchElementException if the iteration has no more elements
+ */
@Override
+ @SuppressWarnings("unchecked")
public E next() {
- if (this.cursor > DynamicArray.this.size) {
+ checkForComodification();
+ if (cursor >= size) {
throw new NoSuchElementException();
}
-
- if (this.cursor > DynamicArray.this.elements.length) {
- throw new ConcurrentModificationException();
- }
-
- final E element = DynamicArray.this.getElement(this.cursor);
- this.cursor++;
-
- return element;
+ return (E) elements[cursor++];
}
+ /**
+ * Removes the last element returned by this iterator.
+ *
+ * @throws IllegalStateException if the next method has not yet been called, or the remove method has already been called after the last call to the next method
+ */
@Override
public void remove() {
- if (this.cursor < 0) {
- throw new IllegalStateException();
+ if (cursor <= 0) {
+ throw new IllegalStateException("Cannot remove element before calling next()");
}
+ checkForComodification();
+ DynamicArray.this.remove(--cursor);
+ expectedModCount = modCount;
+ }
- DynamicArray.this.remove(this.cursor);
- this.cursor--;
+ /**
+ * Checks for concurrent modifications to the array during iteration.
+ *
+ * @throws ConcurrentModificationException if the array has been modified structurally
+ */
+ private void checkForComodification() {
+ if (modCount != expectedModCount) {
+ throw new ConcurrentModificationException();
+ }
}
+ /**
+ * Performs the given action for each remaining element in the iterator until all elements have been processed.
+ *
+ * @param action the action to be performed for each element
+ * @throws NullPointerException if the specified action is null
+ */
@Override
public void forEachRemaining(Consumer super E> action) {
Objects.requireNonNull(action);
-
- for (int i = 0; i < DynamicArray.this.size; i++) {
- action.accept(DynamicArray.this.getElement(i));
+ while (hasNext()) {
+ action.accept(next());
}
}
}
-
- /**
- * This class is the driver for the DynamicArray class it tests a variety
- * of methods and prints the output
- */
- public static void main(String[] args) {
- DynamicArray names = new DynamicArray<>();
- names.add("Peubes");
- names.add("Marley");
-
- for (String name : names) {
- System.out.println(name);
- }
-
- names.stream().forEach(System.out::println);
-
- System.out.println(names);
-
- System.out.println(names.getSize());
-
- names.remove(0);
-
- for (String name : names) {
- System.out.println(name);
- }
- }
}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java b/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java
index 54fb5fba5c1b..741caa59c5b5 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java
@@ -1,25 +1,26 @@
-/*
- Time Complexity = O(E), where E is equal to the number of edges
- */
package com.thealgorithms.datastructures.graphs;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
+/**
+ * AStar class implements the A* pathfinding algorithm to find the shortest path in a graph.
+ * The graph is represented using an adjacency list, and the algorithm uses a heuristic to estimate
+ * the cost to reach the destination node.
+ * Time Complexity = O(E), where E is equal to the number of edges
+ */
public final class AStar {
private AStar() {
}
- private static class Graph {
-
- // Graph's structure can be changed only applying changes to this class.
-
+ /**
+ * Represents a graph using an adjacency list.
+ */
+ static class Graph {
private ArrayList> graph;
- // Initialise ArrayLists in Constructor
Graph(int size) {
this.graph = new ArrayList<>();
for (int i = 0; i < size; i++) {
@@ -31,15 +32,17 @@ private ArrayList getNeighbours(int from) {
return this.graph.get(from);
}
- // Graph is bidirectional, for just one direction remove second instruction of this method.
+ // Add a bidirectional edge to the graph
private void addEdge(Edge edge) {
this.graph.get(edge.getFrom()).add(new Edge(edge.getFrom(), edge.getTo(), edge.getWeight()));
this.graph.get(edge.getTo()).add(new Edge(edge.getTo(), edge.getFrom(), edge.getWeight()));
}
}
+ /**
+ * Represents an edge in the graph with a start node, end node, and weight.
+ */
private static class Edge {
-
private int from;
private int to;
private int weight;
@@ -63,12 +66,13 @@ public int getWeight() {
}
}
- // class to iterate during the algorithm execution, and also used to return the solution.
- private static class PathAndDistance {
-
- private int distance; // distance advanced so far.
- private ArrayList path; // list of visited nodes in this path.
- private int estimated; // heuristic value associated to the last node od the path (current node).
+ /**
+ * Contains information about the path and its total distance.
+ */
+ static class PathAndDistance {
+ private int distance; // total distance from the start node
+ private ArrayList path; // list of nodes in the path
+ private int estimated; // heuristic estimate for reaching the destination
PathAndDistance(int distance, ArrayList path, int estimated) {
this.distance = distance;
@@ -87,112 +91,54 @@ public ArrayList getPath() {
public int getEstimated() {
return estimated;
}
-
- private void printSolution() {
- if (this.path != null) {
- System.out.println("Optimal path: " + this.path + ", distance: " + this.distance);
- } else {
- System.out.println("There is no path available to connect the points");
- }
- }
}
- private static void initializeGraph(Graph graph, ArrayList data) {
+ // Initializes the graph with edges defined in the input data
+ static void initializeGraph(Graph graph, List data) {
for (int i = 0; i < data.size(); i += 4) {
graph.addEdge(new Edge(data.get(i), data.get(i + 1), data.get(i + 2)));
}
- /*
- .x. node
- (y) cost
- - or | or / bidirectional connection
-
- ( 98)- .7. -(86)- .4.
- |
- ( 85)- .17. -(142)- .18. -(92)- .8. -(87)- .11.
- |
- . 1. -------------------- (160)
- | \ |
- (211) \ .6.
- | \ |
- . 5. (101)-.13. -(138) (115)
- | | | /
- ( 99) ( 97) | /
- | | | /
- .12. -(151)- .15. -(80)- .14. | /
- | | | | /
- ( 71) (140) (146)- .2. -(120)
- | | |
- .19. -( 75)- . 0. .10. -(75)- .3.
- | |
- (118) ( 70)
- | |
- .16. -(111)- .9.
- */
- }
-
- public static void main(String[] args) {
- // heuristic function optimistic values
- int[] heuristic = {
- 366,
- 0,
- 160,
- 242,
- 161,
- 178,
- 77,
- 151,
- 226,
- 244,
- 241,
- 234,
- 380,
- 98,
- 193,
- 253,
- 329,
- 80,
- 199,
- 374,
- };
-
- Graph graph = new Graph(20);
- ArrayList graphData = new ArrayList<>(Arrays.asList(0, 19, 75, null, 0, 15, 140, null, 0, 16, 118, null, 19, 12, 71, null, 12, 15, 151, null, 16, 9, 111, null, 9, 10, 70, null, 10, 3, 75, null, 3, 2, 120, null, 2, 14, 146, null, 2, 13, 138, null, 2, 6, 115, null, 15, 14, 80, null,
- 15, 5, 99, null, 14, 13, 97, null, 5, 1, 211, null, 13, 1, 101, null, 6, 1, 160, null, 1, 17, 85, null, 17, 7, 98, null, 7, 4, 86, null, 17, 18, 142, null, 18, 8, 92, null, 8, 11, 87));
- initializeGraph(graph, graphData);
-
- PathAndDistance solution = aStar(3, 1, graph, heuristic);
- solution.printSolution();
}
+ /**
+ * Implements the A* pathfinding algorithm to find the shortest path from a start node to a destination node.
+ *
+ * @param from the starting node
+ * @param to the destination node
+ * @param graph the graph representation of the problem
+ * @param heuristic the heuristic estimates for each node
+ * @return a PathAndDistance object containing the shortest path and its distance
+ */
public static PathAndDistance aStar(int from, int to, Graph graph, int[] heuristic) {
- // nodes are prioritised by the less value of the current distance of their paths, and the
- // estimated value
- // given by the heuristic function to reach the destination point from the current point.
+ // PriorityQueue to explore nodes based on their distance and estimated cost to reach the destination
PriorityQueue queue = new PriorityQueue<>(Comparator.comparingInt(a -> (a.getDistance() + a.getEstimated())));
- // dummy data to start the algorithm from the beginning point
- queue.add(new PathAndDistance(0, new ArrayList<>(List.of(from)), 0));
+ // Start with the initial node
+ queue.add(new PathAndDistance(0, new ArrayList<>(List.of(from)), heuristic[from]));
boolean solutionFound = false;
PathAndDistance currentData = new PathAndDistance(-1, null, -1);
+
while (!queue.isEmpty() && !solutionFound) {
- currentData = queue.poll(); // first in the queue, best node so keep exploring.
- int currentPosition = currentData.getPath().get(currentData.getPath().size() - 1); // current node.
+ currentData = queue.poll(); // get the best node from the queue
+ int currentPosition = currentData.getPath().get(currentData.getPath().size() - 1); // current node
+
+ // Check if the destination has been reached
if (currentPosition == to) {
solutionFound = true;
} else {
for (Edge edge : graph.getNeighbours(currentPosition)) {
- if (!currentData.getPath().contains(edge.getTo())) { // Avoid Cycles
+ // Avoid cycles by checking if the next node is already in the path
+ if (!currentData.getPath().contains(edge.getTo())) {
ArrayList updatedPath = new ArrayList<>(currentData.getPath());
- updatedPath.add(edge.getTo()); // Add the new node to the path, update the distance,
- // and the heuristic function value associated to that path.
+ updatedPath.add(edge.getTo());
+
+ // Update the distance and heuristic for the new path
queue.add(new PathAndDistance(currentData.getDistance() + edge.getWeight(), updatedPath, heuristic[edge.getTo()]));
}
}
}
}
return (solutionFound) ? currentData : new PathAndDistance(-1, null, -1);
- // Out of while loop, if there is a solution, the current Data stores the optimal path, and
- // its distance
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/BipartiteGrapfDFS.java b/src/main/java/com/thealgorithms/datastructures/graphs/BipartiteGrapfDFS.java
deleted file mode 100644
index 4cc14bfd38de..000000000000
--- a/src/main/java/com/thealgorithms/datastructures/graphs/BipartiteGrapfDFS.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package com.thealgorithms.datastructures.graphs;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.Arrays;
-
-/**
- * Given an adjacency list of a graph adj of V no. of vertices having 0 based
- * index. Check whether the graph is bipartite or not.
- *
- * Input : {{0, 1, 0, 1}, {1, 0, 1, 0}, {0, 1, 0, 1}, {1, 0, 1, 0}}
- *
- * Output : YES
- */
-public final class BipartiteGrapfDFS {
- private BipartiteGrapfDFS() {
- }
-
- private static boolean bipartite(int v, ArrayList> adj, int[] color, int node) {
- if (color[node] == -1) {
- color[node] = 1;
- }
- for (Integer it : adj.get(node)) {
- if (color[it] == -1) {
- color[it] = 1 - color[node];
- if (!bipartite(v, adj, color, it)) {
- return false;
- }
- } else if (color[it] == color[node]) {
- return false;
- }
- }
- return true;
- }
-
- public static boolean isBipartite(int v, ArrayList> adj) {
- // Code here
- int[] color = new int[v + 1];
- Arrays.fill(color, -1);
-
- for (int i = 0; i < v; i++) {
- if (color[i] == -1) {
- if (!bipartite(v, adj, color, i)) {
- return false;
- }
- }
- }
- return true;
- }
-
- public static void main(String[] args) throws IOException {
- BufferedReader read = new BufferedReader(new InputStreamReader(System.in));
- int t = Integer.parseInt(read.readLine().trim());
- while (t-- > 0) {
- String[] str1 = read.readLine().trim().split(" ");
- int numVertices = Integer.parseInt(str1[0]);
- int numEdges = Integer.parseInt(str1[1]);
-
- ArrayList> adj = new ArrayList<>();
- for (int i = 0; i < numVertices; i++) {
- adj.add(new ArrayList<>());
- }
- for (int i = 0; i < numEdges; i++) {
- String[] str2 = read.readLine().trim().split(" ");
- int vertexU = Integer.parseInt(str2[0]);
- int vertexV = Integer.parseInt(str2[1]);
- adj.get(vertexU).add(vertexV);
- adj.get(vertexV).add(vertexU);
- }
-
- boolean ans = isBipartite(numVertices, adj);
- if (ans) {
- System.out.println("YES");
- } else {
- System.out.println("NO");
- }
- }
- }
-}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFS.java b/src/main/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFS.java
new file mode 100644
index 000000000000..15ae5225533c
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFS.java
@@ -0,0 +1,83 @@
+package com.thealgorithms.datastructures.graphs;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * This class provides a method to check if a given undirected graph is bipartite using Depth-First Search (DFS).
+ * A bipartite graph is a graph whose vertices can be divided into two disjoint sets such that no two vertices
+ * within the same set are adjacent. In other words, all edges must go between the two sets.
+ *
+ * The implementation leverages DFS to attempt to color the graph using two colors. If we can color the graph such
+ * that no two adjacent vertices have the same color, the graph is bipartite.
+ *
+ * Example:
+ * Input (Adjacency Matrix):
+ * {{0, 1, 0, 1},
+ * {1, 0, 1, 0},
+ * {0, 1, 0, 1},
+ * {1, 0, 1, 0}}
+ *
+ * Output: YES (This graph is bipartite)
+ *
+ * Input (Adjacency Matrix):
+ * {{0, 1, 1, 0},
+ * {1, 0, 1, 0},
+ * {1, 1, 0, 1},
+ * {0, 0, 1, 0}}
+ *
+ * Output: NO (This graph is not bipartite)
+ */
+public final class BipartiteGraphDFS {
+ private BipartiteGraphDFS() {
+ }
+
+ /**
+ * Helper method to perform DFS and check if the graph is bipartite.
+ *
+ * During DFS traversal, this method attempts to color each vertex in such a way
+ * that no two adjacent vertices share the same color.
+ *
+ * @param v Number of vertices in the graph
+ * @param adj Adjacency list of the graph where each index i contains a list of adjacent vertices
+ * @param color Array to store the color assigned to each vertex (-1 indicates uncolored)
+ * @param node Current vertex being processed
+ * @return True if the graph (or component of the graph) is bipartite, otherwise false
+ */
+ private static boolean bipartite(int v, ArrayList> adj, int[] color, int node) {
+ if (color[node] == -1) {
+ color[node] = 1;
+ }
+ for (Integer it : adj.get(node)) {
+ if (color[it] == -1) {
+ color[it] = 1 - color[node];
+ if (!bipartite(v, adj, color, it)) {
+ return false;
+ }
+ } else if (color[it] == color[node]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Method to check if the graph is bipartite.
+ *
+ * @param v Number of vertices in the graph
+ * @param adj Adjacency list of the graph
+ * @return True if the graph is bipartite, otherwise false
+ */
+ public static boolean isBipartite(int v, ArrayList> adj) {
+ int[] color = new int[v + 1];
+ Arrays.fill(color, -1);
+ for (int i = 0; i < v; i++) {
+ if (color[i] == -1) {
+ if (!bipartite(v, adj, color, i)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java b/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java
index b67c5512e622..aea2b74bd13b 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java
@@ -6,7 +6,6 @@
class Cycle {
private final int nodes;
- private final int edges;
private int[][] adjacencyMatrix;
private boolean[] visited;
ArrayList> cycles = new ArrayList>();
@@ -16,7 +15,7 @@ class Cycle {
System.out.print("Enter the no. of nodes: ");
nodes = in.nextInt();
System.out.print("Enter the no. of Edges: ");
- edges = in.nextInt();
+ final int edges = in.nextInt();
adjacencyMatrix = new int[nodes][nodes];
visited = new boolean[nodes];
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/DIJSKSTRAS_ALGORITHM.java b/src/main/java/com/thealgorithms/datastructures/graphs/DIJSKSTRAS_ALGORITHM.java
deleted file mode 100644
index 419da4a9be73..000000000000
--- a/src/main/java/com/thealgorithms/datastructures/graphs/DIJSKSTRAS_ALGORITHM.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
-Refer https://www.geeksforgeeks.org/dijkstras-shortest-path-algorithm-greedy-algo-7/
-for better understanding
- */
-package com.thealgorithms.datastructures.graphs;
-
-class Dijkstras {
-
- int k = 9;
-
- int minDist(int[] dist, Boolean[] set) {
- int min = Integer.MAX_VALUE;
- int minIndex = -1;
-
- for (int r = 0; r < k; r++) {
- if (!set[r] && dist[r] <= min) {
- min = dist[r];
- minIndex = r;
- }
- }
-
- return minIndex;
- }
-
- void print(int[] dist) {
- System.out.println("Vertex \t\t Distance");
- for (int i = 0; i < k; i++) {
- System.out.println(i + " \t " + dist[i]);
- }
- }
-
- void dijkstra(int[][] graph, int src) {
- int[] dist = new int[k];
- Boolean[] set = new Boolean[k];
-
- for (int i = 0; i < k; i++) {
- dist[i] = Integer.MAX_VALUE;
- set[i] = Boolean.FALSE;
- }
-
- dist[src] = 0;
-
- for (int c = 0; c < k - 1; c++) {
- int u = minDist(dist, set);
-
- set[u] = Boolean.TRUE;
-
- for (int v = 0; v < k; v++) {
- if (!set[v] && graph[u][v] != 0 && dist[u] != Integer.MAX_VALUE && dist[u] + graph[u][v] < dist[v]) {
- dist[v] = dist[u] + graph[u][v];
- }
- }
- }
-
- print(dist);
- }
-
- public static void main(String[] args) {
- int[][] graph = new int[][] {
- {0, 4, 0, 0, 0, 0, 0, 8, 0},
- {4, 0, 8, 0, 0, 0, 0, 11, 0},
- {0, 8, 0, 7, 0, 4, 0, 0, 2},
- {0, 0, 7, 0, 9, 14, 0, 0, 0},
- {0, 0, 0, 9, 0, 10, 0, 0, 0},
- {0, 0, 4, 14, 10, 0, 2, 0, 0},
- {0, 0, 0, 0, 0, 2, 0, 1, 6},
- {8, 11, 0, 0, 0, 0, 1, 0, 7},
- {0, 0, 2, 0, 0, 0, 6, 7, 0},
- };
- Dijkstras t = new Dijkstras();
- t.dijkstra(graph, 0);
- } // main
-} // djikstras
-/*
-OUTPUT :
-Vertex Distance
-0 0
-1 4
-2 12
-3 19
-4 21
-5 11
-6 9
-7 8
-8 14
- */
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithm.java
new file mode 100644
index 000000000000..70699a9461f7
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithm.java
@@ -0,0 +1,91 @@
+package com.thealgorithms.datastructures.graphs;
+
+import java.util.Arrays;
+
+/**
+ * Dijkstra's algorithm for finding the shortest path from a single source vertex to all other vertices in a graph.
+ */
+public class DijkstraAlgorithm {
+
+ private final int vertexCount;
+
+ /**
+ * Constructs a Dijkstra object with the given number of vertices.
+ *
+ * @param vertexCount The number of vertices in the graph.
+ */
+ public DijkstraAlgorithm(int vertexCount) {
+ this.vertexCount = vertexCount;
+ }
+
+ /**
+ * Executes Dijkstra's algorithm on the provided graph to find the shortest paths from the source vertex to all other vertices.
+ *
+ * The graph is represented as an adjacency matrix where {@code graph[i][j]} represents the weight of the edge from vertex {@code i}
+ * to vertex {@code j}. A value of 0 indicates no edge exists between the vertices.
+ *
+ * @param graph The graph represented as an adjacency matrix.
+ * @param source The source vertex.
+ * @return An array where the value at each index {@code i} represents the shortest distance from the source vertex to vertex {@code i}.
+ * @throws IllegalArgumentException if the source vertex is out of range.
+ */
+ public int[] run(int[][] graph, int source) {
+ if (source < 0 || source >= vertexCount) {
+ throw new IllegalArgumentException("Incorrect source");
+ }
+
+ int[] distances = new int[vertexCount];
+ boolean[] processed = new boolean[vertexCount];
+
+ Arrays.fill(distances, Integer.MAX_VALUE);
+ Arrays.fill(processed, false);
+ distances[source] = 0;
+
+ for (int count = 0; count < vertexCount - 1; count++) {
+ int u = getMinDistanceVertex(distances, processed);
+ processed[u] = true;
+
+ for (int v = 0; v < vertexCount; v++) {
+ if (!processed[v] && graph[u][v] != 0 && distances[u] != Integer.MAX_VALUE && distances[u] + graph[u][v] < distances[v]) {
+ distances[v] = distances[u] + graph[u][v];
+ }
+ }
+ }
+
+ printDistances(distances);
+ return distances;
+ }
+
+ /**
+ * Finds the vertex with the minimum distance value from the set of vertices that have not yet been processed.
+ *
+ * @param distances The array of current shortest distances from the source vertex.
+ * @param processed The array indicating whether each vertex has been processed.
+ * @return The index of the vertex with the minimum distance value.
+ */
+ private int getMinDistanceVertex(int[] distances, boolean[] processed) {
+ int min = Integer.MAX_VALUE;
+ int minIndex = -1;
+
+ for (int v = 0; v < vertexCount; v++) {
+ if (!processed[v] && distances[v] <= min) {
+ min = distances[v];
+ minIndex = v;
+ }
+ }
+
+ return minIndex;
+ }
+
+ /**
+ * Prints the shortest distances from the source vertex to all other vertices.
+ *
+ * @param distances The array of shortest distances.
+ */
+ private void printDistances(int[] distances) {
+ System.out.println("Vertex \t Distance");
+ for (int i = 0; i < vertexCount; i++) {
+ System.out.println(i + " \t " + distances[i]);
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java
new file mode 100644
index 000000000000..a686b808a970
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java
@@ -0,0 +1,66 @@
+package com.thealgorithms.datastructures.graphs;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.TreeSet;
+import org.apache.commons.lang3.tuple.Pair;
+
+/**
+ * Dijkstra's algorithm for finding the shortest path from a single source vertex to all other vertices in a graph.
+ */
+public class DijkstraOptimizedAlgorithm {
+
+ private final int vertexCount;
+
+ /**
+ * Constructs a Dijkstra object with the given number of vertices.
+ *
+ * @param vertexCount The number of vertices in the graph.
+ */
+ public DijkstraOptimizedAlgorithm(int vertexCount) {
+ this.vertexCount = vertexCount;
+ }
+
+ /**
+ * Executes Dijkstra's algorithm on the provided graph to find the shortest paths from the source vertex to all other vertices.
+ *
+ * The graph is represented as an adjacency matrix where {@code graph[i][j]} represents the weight of the edge from vertex {@code i}
+ * to vertex {@code j}. A value of 0 indicates no edge exists between the vertices.
+ *
+ * @param graph The graph represented as an adjacency matrix.
+ * @param source The source vertex.
+ * @return An array where the value at each index {@code i} represents the shortest distance from the source vertex to vertex {@code i}.
+ * @throws IllegalArgumentException if the source vertex is out of range.
+ */
+ public int[] run(int[][] graph, int source) {
+ if (source < 0 || source >= vertexCount) {
+ throw new IllegalArgumentException("Incorrect source");
+ }
+
+ int[] distances = new int[vertexCount];
+ boolean[] processed = new boolean[vertexCount];
+ Set> unprocessed = new TreeSet<>();
+
+ Arrays.fill(distances, Integer.MAX_VALUE);
+ Arrays.fill(processed, false);
+ distances[source] = 0;
+ unprocessed.add(Pair.of(0, source));
+
+ while (!unprocessed.isEmpty()) {
+ Pair distanceAndU = unprocessed.iterator().next();
+ unprocessed.remove(distanceAndU);
+ int u = distanceAndU.getRight();
+ processed[u] = true;
+
+ for (int v = 0; v < vertexCount; v++) {
+ if (!processed[v] && graph[u][v] != 0 && distances[u] != Integer.MAX_VALUE && distances[u] + graph[u][v] < distances[v]) {
+ unprocessed.remove(Pair.of(distances[v], v));
+ distances[v] = distances[u] + graph[u][v];
+ unprocessed.add(Pair.of(distances[v], v));
+ }
+ }
+ }
+
+ return distances;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithm.java
new file mode 100644
index 000000000000..db716580d689
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithm.java
@@ -0,0 +1,251 @@
+package com.thealgorithms.datastructures.graphs;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+/**
+ * The EdmondsBlossomAlgorithm class implements Edmonds' Blossom Algorithm
+ * to find the maximum matching in a general graph. The algorithm efficiently
+ * handles cases where the graph contains odd-length cycles by contracting
+ * "blossoms" and finding augmenting paths.
+ *
+ * Documentation of Algorithm (Stanford University)
+ *
+ * Wikipedia Documentation
+ */
+public final class EdmondsBlossomAlgorithm {
+
+ private EdmondsBlossomAlgorithm() {
+ }
+
+ private static final int UNMATCHED = -1; // Constant to represent unmatched vertices
+
+ /**
+ * Finds the maximum matching in a general graph (Edmonds Blossom Algorithm).
+ *
+ * @param edges A list of edges in the graph.
+ * @param vertexCount The number of vertices in the graph.
+ * @return A list of matched pairs of vertices.
+ */
+ public static List maximumMatching(Iterable edges, int vertexCount) {
+ List> graph = new ArrayList<>(vertexCount);
+
+ // Initialize each vertex's adjacency list.
+ for (int i = 0; i < vertexCount; i++) {
+ graph.add(new ArrayList<>());
+ }
+
+ // Populate the graph with the edges
+ for (int[] edge : edges) {
+ int u = edge[0];
+ int v = edge[1];
+ graph.get(u).add(v);
+ graph.get(v).add(u);
+ }
+
+ // Initial matching array and auxiliary data structures
+ int[] match = new int[vertexCount];
+ Arrays.fill(match, UNMATCHED); // All vertices are initially unmatched
+ int[] parent = new int[vertexCount];
+ int[] base = new int[vertexCount];
+ boolean[] inBlossom = new boolean[vertexCount]; // Indicates if a vertex is part of a blossom
+ boolean[] inQueue = new boolean[vertexCount]; // Tracks vertices in the BFS queue
+
+ // Main logic for finding maximum matching
+ for (int u = 0; u < vertexCount; u++) {
+ if (match[u] == UNMATCHED) {
+ // BFS initialization
+ Arrays.fill(parent, UNMATCHED);
+ for (int i = 0; i < vertexCount; i++) {
+ base[i] = i; // Each vertex is its own base initially
+ }
+ Arrays.fill(inBlossom, false);
+ Arrays.fill(inQueue, false);
+
+ Queue queue = new LinkedList<>();
+ queue.add(u);
+ inQueue[u] = true;
+
+ boolean augmentingPathFound = false;
+
+ // BFS to find augmenting paths
+ while (!queue.isEmpty() && !augmentingPathFound) {
+ int current = queue.poll(); // Use a different name for clarity
+ for (int y : graph.get(current)) {
+ // Skip if we are looking at the same edge as the current match
+ if (match[current] == y) {
+ continue;
+ }
+
+ if (base[current] == base[y]) {
+ continue; // Avoid self-loops
+ }
+
+ if (parent[y] == UNMATCHED) {
+ // Case 1: y is unmatched, we've found an augmenting path
+ if (match[y] == UNMATCHED) {
+ parent[y] = current;
+ augmentingPathFound = true;
+ updateMatching(match, parent, y); // Augment along this path
+ break;
+ }
+
+ // Case 2: y is matched, add y's match to the queue
+ int z = match[y];
+ parent[y] = current;
+ parent[z] = y;
+ if (!inQueue[z]) {
+ queue.add(z);
+ inQueue[z] = true;
+ }
+ } else {
+ // Case 3: Both x and y have a parent; check for a cycle/blossom
+ int baseU = findBase(base, parent, current, y);
+ if (baseU != UNMATCHED) {
+ contractBlossom(new BlossomData(new BlossomAuxData(queue, parent, base, inBlossom, match, inQueue), current, y, baseU));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Create result list of matched pairs
+ List matchingResult = new ArrayList<>();
+ for (int v = 0; v < vertexCount; v++) {
+ if (match[v] != UNMATCHED && v < match[v]) {
+ matchingResult.add(new int[] {v, match[v]});
+ }
+ }
+
+ return matchingResult;
+ }
+
+ /**
+ * Updates the matching along the augmenting path found.
+ *
+ * @param match The matching array.
+ * @param parent The parent array used during the BFS.
+ * @param u The starting node of the augmenting path.
+ */
+ private static void updateMatching(int[] match, int[] parent, int u) {
+ while (u != UNMATCHED) {
+ int v = parent[u];
+ int next = match[v];
+ match[v] = u;
+ match[u] = v;
+ u = next;
+ }
+ }
+
+ /**
+ * Finds the base of a node in the blossom.
+ *
+ * @param base The base array.
+ * @param parent The parent array.
+ * @param u One end of the edge.
+ * @param v The other end of the edge.
+ * @return The base of the node or UNMATCHED.
+ */
+ private static int findBase(int[] base, int[] parent, int u, int v) {
+ boolean[] visited = new boolean[base.length];
+
+ // Mark ancestors of u
+ int currentU = u;
+ while (true) {
+ currentU = base[currentU]; // Move assignment out of the condition
+ visited[currentU] = true;
+ if (parent[currentU] == UNMATCHED) {
+ break;
+ }
+ currentU = parent[currentU]; // Move assignment out of the condition
+ }
+
+ // Find the common ancestor of v
+ int currentV = v;
+ while (true) {
+ currentV = base[currentV]; // Move assignment out of the condition
+ if (visited[currentV]) {
+ return currentV;
+ }
+ currentV = parent[currentV]; // Move assignment out of the condition
+ }
+ }
+
+ /**
+ * Contracts a blossom and updates the base array.
+ *
+ * @param blossomData The data containing the parameters related to the blossom contraction.
+ */
+ private static void contractBlossom(BlossomData blossomData) {
+ for (int x = blossomData.u; blossomData.auxData.base[x] != blossomData.lca; x = blossomData.auxData.parent[blossomData.auxData.match[x]]) {
+ int baseX = blossomData.auxData.base[x];
+ int matchBaseX = blossomData.auxData.base[blossomData.auxData.match[x]];
+
+ // Split the inner assignment into two separate assignments
+ blossomData.auxData.inBlossom[baseX] = true;
+ blossomData.auxData.inBlossom[matchBaseX] = true;
+ }
+
+ for (int x = blossomData.v; blossomData.auxData.base[x] != blossomData.lca; x = blossomData.auxData.parent[blossomData.auxData.match[x]]) {
+ int baseX = blossomData.auxData.base[x];
+ int matchBaseX = blossomData.auxData.base[blossomData.auxData.match[x]];
+
+ // Split the inner assignment into two separate assignments
+ blossomData.auxData.inBlossom[baseX] = true;
+ blossomData.auxData.inBlossom[matchBaseX] = true;
+ }
+
+ // Update the base for all marked vertices
+ for (int i = 0; i < blossomData.auxData.base.length; i++) {
+ if (blossomData.auxData.inBlossom[blossomData.auxData.base[i]]) {
+ blossomData.auxData.base[i] = blossomData.lca; // Contract to the lowest common ancestor
+ if (!blossomData.auxData.inQueue[i]) {
+ blossomData.auxData.queue.add(i); // Add to queue if not already present
+ blossomData.auxData.inQueue[i] = true;
+ }
+ }
+ }
+ }
+
+ /**
+ * Auxiliary data class to encapsulate common parameters for the blossom operations.
+ */
+ static class BlossomAuxData {
+ Queue queue; // Queue for BFS traversal
+ int[] parent; // Parent array to store the paths
+ int[] base; // Base array to track the base of each vertex
+ boolean[] inBlossom; // Flags to indicate if a vertex is in a blossom
+ int[] match; // Array to store matches for each vertex
+ boolean[] inQueue; // Flags to track vertices in the BFS queue
+
+ BlossomAuxData(Queue queue, int[] parent, int[] base, boolean[] inBlossom, int[] match, boolean[] inQueue) {
+ this.queue = queue;
+ this.parent = parent;
+ this.base = base;
+ this.inBlossom = inBlossom;
+ this.match = match;
+ this.inQueue = inQueue;
+ }
+ }
+
+ /**
+ * BlossomData class with reduced parameters.
+ */
+ static class BlossomData {
+ BlossomAuxData auxData; // Use the auxiliary data class
+ int u; // One vertex in the edge
+ int v; // Another vertex in the edge
+ int lca; // Lowest Common Ancestor
+
+ BlossomData(BlossomAuxData auxData, int u, int v, int lca) {
+ this.auxData = auxData;
+ this.u = u;
+ this.v = v;
+ this.lca = lca;
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/FloydWarshall.java b/src/main/java/com/thealgorithms/datastructures/graphs/FloydWarshall.java
index d47ffe3aa27d..e5e673a21794 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/FloydWarshall.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/FloydWarshall.java
@@ -1,42 +1,75 @@
package com.thealgorithms.datastructures.graphs;
-import java.util.Scanner;
-
+/**
+ * The {@code FloydWarshall} class provides an implementation of the Floyd-Warshall algorithm
+ * to compute the shortest paths between all pairs of vertices in a weighted graph.
+ * It handles both positive and negative edge weights but does not support negative cycles.
+ * The algorithm is based on dynamic programming and runs in O(V^3) time complexity,
+ * where V is the number of vertices in the graph.
+ *
+ *
+ * The distance matrix is updated iteratively to find the shortest distance between any two vertices
+ * by considering each vertex as an intermediate step.
+ *
+ *
+ * Reference: Floyd-Warshall Algorithm
+ */
public class FloydWarshall {
private int[][] distanceMatrix;
- private int numberofvertices; // number of vertices in the graph
+ private int numberofvertices;
public static final int INFINITY = 999;
+ /**
+ * Constructs a Floyd-Warshall instance for a graph with the given number of vertices.
+ * Initializes the distance matrix for the graph.
+ *
+ * @param numberofvertices The number of vertices in the graph.
+ */
public FloydWarshall(int numberofvertices) {
- distanceMatrix = new int[numberofvertices + 1][numberofvertices + 1]; // stores the value of distance from all the possible path form the source
- // vertex to destination vertex
+ distanceMatrix = new int[numberofvertices + 1][numberofvertices + 1];
// The matrix is initialized with 0's by default
this.numberofvertices = numberofvertices;
}
- public void floydwarshall(int[][] adjacencyMatrix) { // calculates all the distances from source to destination vertex
+ /**
+ * Executes the Floyd-Warshall algorithm to compute the shortest path between all pairs of vertices.
+ * It uses an adjacency matrix to calculate the distance matrix by considering each vertex as an intermediate point.
+ *
+ * @param adjacencyMatrix The weighted adjacency matrix representing the graph.
+ * A value of 0 means no direct edge between the vertices, except for diagonal elements which are 0 (distance to self).
+ */
+ public void floydwarshall(int[][] adjacencyMatrix) {
+ // Initialize the distance matrix with the adjacency matrix.
for (int source = 1; source <= numberofvertices; source++) {
- for (int destination = 1; destination <= numberofvertices; destination++) {
- distanceMatrix[source][destination] = adjacencyMatrix[source][destination];
- }
+ System.arraycopy(adjacencyMatrix[source], 1, distanceMatrix[source], 1, numberofvertices);
}
for (int intermediate = 1; intermediate <= numberofvertices; intermediate++) {
for (int source = 1; source <= numberofvertices; source++) {
for (int destination = 1; destination <= numberofvertices; destination++) {
- if (distanceMatrix[source][intermediate] + distanceMatrix[intermediate][destination] < distanceMatrix[source][destination]) { // calculated distance it get replaced as
- // new shortest distance // if the new
- // distance calculated is less then the
- // earlier shortest
+ // Update distance if a shorter path through the intermediate vertex exists.
+ if (distanceMatrix[source][intermediate] + distanceMatrix[intermediate][destination] < distanceMatrix[source][destination]) {
distanceMatrix[source][destination] = distanceMatrix[source][intermediate] + distanceMatrix[intermediate][destination];
}
}
}
}
+
+ printDistanceMatrix();
+ }
+
+ /**
+ * Prints the distance matrix representing the shortest paths between all pairs of vertices.
+ * The rows and columns correspond to the source and destination vertices.
+ */
+ private void printDistanceMatrix() {
+ // Print header for vertices
for (int source = 1; source <= numberofvertices; source++) {
System.out.print("\t" + source);
}
System.out.println();
+
+ // Print the distance matrix
for (int source = 1; source <= numberofvertices; source++) {
System.out.print(source + "\t");
for (int destination = 1; destination <= numberofvertices; destination++) {
@@ -46,27 +79,7 @@ public void floydwarshall(int[][] adjacencyMatrix) { // calculates all the dista
}
}
- public static void main(String... arg) {
- Scanner scan = new Scanner(System.in);
- System.out.println("Enter the number of vertices");
- int numberOfVertices = scan.nextInt();
- int[][] adjacencyMatrix = new int[numberOfVertices + 1][numberOfVertices + 1];
- System.out.println("Enter the Weighted Matrix for the graph");
- for (int source = 1; source <= numberOfVertices; source++) {
- for (int destination = 1; destination <= numberOfVertices; destination++) {
- adjacencyMatrix[source][destination] = scan.nextInt();
- if (source == destination) {
- adjacencyMatrix[source][destination] = 0;
- continue;
- }
- if (adjacencyMatrix[source][destination] == 0) {
- adjacencyMatrix[source][destination] = INFINITY;
- }
- }
- }
- System.out.println("The Transitive Closure of the Graph");
- FloydWarshall floydwarshall = new FloydWarshall(numberOfVertices);
- floydwarshall.floydwarshall(adjacencyMatrix);
- scan.close();
+ public Object[] getDistanceMatrix() {
+ return distanceMatrix;
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java b/src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java
new file mode 100644
index 000000000000..b3a2053d54b5
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java
@@ -0,0 +1,75 @@
+package com.thealgorithms.datastructures.graphs;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * This class implements the Ford-Fulkerson algorithm to compute the maximum flow
+ * in a flow network.
+ *
+ * The algorithm uses breadth-first search (BFS) to find augmenting paths from
+ * the source vertex to the sink vertex, updating the flow in the network until
+ * no more augmenting paths can be found.
+ */
+public final class FordFulkerson {
+ private static final int INF = Integer.MAX_VALUE;
+
+ private FordFulkerson() {
+ }
+
+ /**
+ * Computes the maximum flow in a flow network using the Ford-Fulkerson algorithm.
+ *
+ * @param vertexCount the number of vertices in the flow network
+ * @param capacity a 2D array representing the capacity of edges in the network
+ * @param flow a 2D array representing the current flow in the network
+ * @param source the source vertex in the flow network
+ * @param sink the sink vertex in the flow network
+ * @return the total maximum flow from the source to the sink
+ */
+ public static int networkFlow(int vertexCount, int[][] capacity, int[][] flow, int source, int sink) {
+ int totalFlow = 0;
+
+ while (true) {
+ int[] parent = new int[vertexCount];
+ boolean[] visited = new boolean[vertexCount];
+ Queue queue = new LinkedList<>();
+
+ queue.add(source);
+ visited[source] = true;
+ parent[source] = -1;
+
+ while (!queue.isEmpty() && !visited[sink]) {
+ int current = queue.poll();
+
+ for (int next = 0; next < vertexCount; next++) {
+ if (!visited[next] && capacity[current][next] - flow[current][next] > 0) {
+ queue.add(next);
+ visited[next] = true;
+ parent[next] = current;
+ }
+ }
+ }
+
+ if (!visited[sink]) {
+ break; // No more augmenting paths
+ }
+
+ int pathFlow = INF;
+ for (int v = sink; v != source; v = parent[v]) {
+ int u = parent[v];
+ pathFlow = Math.min(pathFlow, capacity[u][v] - flow[u][v]);
+ }
+
+ for (int v = sink; v != source; v = parent[v]) {
+ int u = parent[v];
+ flow[u][v] += pathFlow;
+ flow[v][u] -= pathFlow;
+ }
+
+ totalFlow += pathFlow;
+ }
+
+ return totalFlow;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java b/src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java
index f12e3892b1b2..5c95850c4971 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java
@@ -1,8 +1,14 @@
package com.thealgorithms.datastructures.graphs;
+import java.util.Arrays;
+
/**
- * Java program for Hamiltonian Cycle
- * wikipedia
+ * Java program to find a Hamiltonian Cycle in a graph.
+ * A Hamiltonian Cycle is a cycle that visits every vertex exactly once
+ * and returns to the starting vertex.
+ *
+ * For more details, see the
+ * Wikipedia article.
*
* @author Akshay Dubey
*/
@@ -14,30 +20,30 @@ public class HamiltonianCycle {
private int[][] graph;
/**
- * Find hamiltonian cycle for given graph G(V,E)
+ * Finds a Hamiltonian Cycle for the given graph.
*
- * @param graph Adjacency matrix of a graph G(V, E)
- * for which hamiltonian path is to be found
- * @return Array containing hamiltonian cycle
- * else returns 1D array with value -1.
+ * @param graph Adjacency matrix representing the graph G(V, E), where V is
+ * the set of vertices and E is the set of edges.
+ * @return An array representing the Hamiltonian cycle if found, otherwise an
+ * array filled with -1 indicating no Hamiltonian cycle exists.
*/
public int[] findHamiltonianCycle(int[][] graph) {
+ // Single vertex graph
+ if (graph.length == 1) {
+ return new int[] {0, 0};
+ }
+
this.vertex = graph.length;
this.cycle = new int[this.vertex + 1];
- // Initialize path array with -1 value
- for (int i = 0; i < this.cycle.length; i++) {
- this.cycle[i] = -1;
- }
+ // Initialize the cycle array with -1 to represent unvisited vertices
+ Arrays.fill(this.cycle, -1);
this.graph = graph;
-
this.cycle[0] = 0;
this.pathCount = 1;
if (!isPathFound(0)) {
- for (int i = 0; i < this.cycle.length; i++) {
- this.cycle[i] = -1;
- }
+ Arrays.fill(this.cycle, -1);
} else {
this.cycle[this.cycle.length - 1] = this.cycle[0];
}
@@ -46,11 +52,10 @@ public int[] findHamiltonianCycle(int[][] graph) {
}
/**
- * function to find paths recursively
- * Find paths recursively from given vertex
+ * Recursively searches for a Hamiltonian cycle from the given vertex.
*
- * @param vertex Vertex from which path is to be found
- * @returns true if path is found false otherwise
+ * @param vertex The current vertex from which to explore paths.
+ * @return {@code true} if a Hamiltonian cycle is found, otherwise {@code false}.
*/
public boolean isPathFound(int vertex) {
boolean isLastVertexConnectedToStart = this.graph[vertex][0] == 1 && this.pathCount == this.vertex;
@@ -58,31 +63,26 @@ public boolean isPathFound(int vertex) {
return true;
}
- /* all vertices selected but last vertex not linked to 0 **/
+ // If all vertices are visited but the last vertex is not connected to the start
if (this.pathCount == this.vertex) {
return false;
}
for (int v = 0; v < this.vertex; v++) {
- /* if connected **/
- if (this.graph[vertex][v] == 1) {
- /* add to path **/
- this.cycle[this.pathCount++] = v;
-
- /* remove connection **/
+ if (this.graph[vertex][v] == 1) { // Check if there is an edge
+ this.cycle[this.pathCount++] = v; // Add the vertex to the cycle
this.graph[vertex][v] = 0;
this.graph[v][vertex] = 0;
- /* if vertex not already selected solve recursively **/
+ // Recursively attempt to complete the cycle
if (!isPresent(v)) {
return isPathFound(v);
}
- /* restore connection **/
+ // Restore the edge if the path does not work
this.graph[vertex][v] = 1;
this.graph[v][vertex] = 1;
- /* remove path **/
this.cycle[--this.pathCount] = -1;
}
}
@@ -90,10 +90,10 @@ public boolean isPathFound(int vertex) {
}
/**
- * function to check if path is already selected
- * Check if path is already selected
+ * Checks if a vertex is already part of the current Hamiltonian path.
*
- * @param vertex Starting vertex
+ * @param vertex The vertex to check.
+ * @return {@code true} if the vertex is already in the path, otherwise {@code false}.
*/
public boolean isPresent(int vertex) {
for (int i = 0; i < pathCount - 1; i++) {
@@ -101,7 +101,6 @@ public boolean isPresent(int vertex) {
return true;
}
}
-
return false;
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithm.java
new file mode 100644
index 000000000000..351bd5b009e8
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithm.java
@@ -0,0 +1,200 @@
+package com.thealgorithms.datastructures.graphs;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This class implements Johnson's algorithm for finding all-pairs shortest paths in a weighted,
+ * directed graph that may contain negative edge weights.
+ *
+ * Johnson's algorithm works by using the Bellman-Ford algorithm to compute a transformation of the
+ * input graph that removes all negative weights, allowing Dijkstra's algorithm to be used for
+ * efficient shortest path computations.
+ *
+ * Time Complexity: O(V^2 * log(V) + V*E)
+ * Space Complexity: O(V^2)
+ *
+ * Where V is the number of vertices and E is the number of edges in the graph.
+ *
+ * For more information, please visit {@link https://en.wikipedia.org/wiki/Johnson%27s_algorithm}
+ */
+public final class JohnsonsAlgorithm {
+
+ private static final double INF = Double.POSITIVE_INFINITY;
+
+ private JohnsonsAlgorithm() {
+ }
+
+ /**
+ * Executes Johnson's algorithm on the given graph.
+ * Steps:
+ * 1. Add a new vertex to the graph and run Bellman-Ford to compute modified weights
+ * 2. t the graph using the modified weights
+ * 3. Run Dijkstra's algorithm for each vertex to compute the shortest paths
+ * The final result is a 2D array of shortest distances between all pairs of vertices.
+ *
+ * @param graph The input graph represented as an adjacency matrix.
+ * @return A 2D array representing the shortest distances between all pairs of vertices.
+ */
+ public static double[][] johnsonAlgorithm(double[][] graph) {
+ int numVertices = graph.length;
+ double[][] edges = convertToEdgeList(graph);
+
+ double[] modifiedWeights = bellmanFord(edges, numVertices);
+
+ double[][] reweightedGraph = reweightGraph(graph, modifiedWeights);
+
+ double[][] shortestDistances = new double[numVertices][numVertices];
+ for (int source = 0; source < numVertices; source++) {
+ shortestDistances[source] = dijkstra(reweightedGraph, source, modifiedWeights);
+ }
+
+ return shortestDistances;
+ }
+
+ /**
+ * Converts the adjacency matrix representation of the graph to an edge list.
+ *
+ * @param graph The input graph as an adjacency matrix.
+ * @return An array of edges, where each edge is represented as [from, to, weight].
+ */
+ public static double[][] convertToEdgeList(double[][] graph) {
+ int numVertices = graph.length;
+ List edgeList = new ArrayList<>();
+
+ for (int i = 0; i < numVertices; i++) {
+ for (int j = 0; j < numVertices; j++) {
+ if (i != j && !Double.isInfinite(graph[i][j])) {
+ // Only add edges that are not self-loops and have a finite weight
+ edgeList.add(new double[] {i, j, graph[i][j]});
+ }
+ }
+ }
+
+ return edgeList.toArray(new double[0][]);
+ }
+
+ /**
+ * Implements the Bellman-Ford algorithm to compute the shortest paths from a new vertex
+ * to all other vertices. This is used to calculate the weight function h(v) for reweighting.
+ *
+ * @param edges The edge list of the graph.
+ * @param numVertices The number of vertices in the original graph.
+ * @return An array of modified weights for each vertex.
+ */
+ private static double[] bellmanFord(double[][] edges, int numVertices) {
+ double[] dist = new double[numVertices + 1];
+ Arrays.fill(dist, INF);
+ dist[numVertices] = 0;
+
+ // Add edges from the new vertex to all original vertices
+ double[][] allEdges = Arrays.copyOf(edges, edges.length + numVertices);
+ for (int i = 0; i < numVertices; i++) {
+ allEdges[edges.length + i] = new double[] {numVertices, i, 0};
+ }
+
+ // Relax all edges V times
+ for (int i = 0; i < numVertices; i++) {
+ for (double[] edge : allEdges) {
+ int u = (int) edge[0];
+ int v = (int) edge[1];
+ double weight = edge[2];
+ if (dist[u] != INF && dist[u] + weight < dist[v]) {
+ dist[v] = dist[u] + weight;
+ }
+ }
+ }
+
+ // Check for negative weight cycles
+ for (double[] edge : allEdges) {
+ int u = (int) edge[0];
+ int v = (int) edge[1];
+ double weight = edge[2];
+ if (dist[u] + weight < dist[v]) {
+ throw new IllegalArgumentException("Graph contains a negative weight cycle");
+ }
+ }
+
+ return Arrays.copyOf(dist, numVertices);
+ }
+
+ /**
+ * Reweights the graph using the modified weights computed by Bellman-Ford.
+ *
+ * @param graph The original graph.
+ * @param modifiedWeights The modified weights from Bellman-Ford.
+ * @return The reweighted graph.
+ */
+ public static double[][] reweightGraph(double[][] graph, double[] modifiedWeights) {
+ int numVertices = graph.length;
+ double[][] reweightedGraph = new double[numVertices][numVertices];
+
+ for (int i = 0; i < numVertices; i++) {
+ for (int j = 0; j < numVertices; j++) {
+ if (graph[i][j] != 0) {
+ // New weight = original weight + h(u) - h(v)
+ reweightedGraph[i][j] = graph[i][j] + modifiedWeights[i] - modifiedWeights[j];
+ }
+ }
+ }
+
+ return reweightedGraph;
+ }
+
+ /**
+ * Implements Dijkstra's algorithm for finding shortest paths from a source vertex.
+ *
+ * @param reweightedGraph The reweighted graph to run Dijkstra's on.
+ * @param source The source vertex.
+ * @param modifiedWeights The modified weights from Bellman-Ford.
+ * @return An array of shortest distances from the source to all other vertices.
+ */
+ public static double[] dijkstra(double[][] reweightedGraph, int source, double[] modifiedWeights) {
+ int numVertices = reweightedGraph.length;
+ double[] dist = new double[numVertices];
+ boolean[] visited = new boolean[numVertices];
+ Arrays.fill(dist, INF);
+ dist[source] = 0;
+
+ for (int count = 0; count < numVertices - 1; count++) {
+ int u = minDistance(dist, visited);
+ visited[u] = true;
+
+ for (int v = 0; v < numVertices; v++) {
+ if (!visited[v] && reweightedGraph[u][v] != 0 && dist[u] != INF && dist[u] + reweightedGraph[u][v] < dist[v]) {
+ dist[v] = dist[u] + reweightedGraph[u][v];
+ }
+ }
+ }
+
+ // Adjust distances back to the original graph weights
+ for (int i = 0; i < numVertices; i++) {
+ if (dist[i] != INF) {
+ dist[i] = dist[i] - modifiedWeights[source] + modifiedWeights[i];
+ }
+ }
+
+ return dist;
+ }
+
+ /**
+ * Finds the vertex with the minimum distance value from the set of vertices
+ * not yet included in the shortest path tree.
+ *
+ * @param dist Array of distances.
+ * @param visited Array of visited vertices.
+ * @return The index of the vertex with minimum distance.
+ */
+ public static int minDistance(double[] dist, boolean[] visited) {
+ double min = INF;
+ int minIndex = -1;
+ for (int v = 0; v < dist.length; v++) {
+ if (!visited[v] && dist[v] <= min) {
+ min = dist[v];
+ minIndex = v;
+ }
+ }
+ return minIndex;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java
index d5035cf625a6..9a97bc3f4808 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java
@@ -9,96 +9,120 @@
import java.util.Set;
/**
- * A class that represents the adjaceny list of a graph
+ * A class representing the adjacency list of a directed graph. The adjacency list
+ * maintains a mapping of vertices to their adjacent vertices.
+ *
+ * @param the type of vertices, extending Comparable to ensure that vertices
+ * can be compared
*/
class AdjacencyList> {
Map> adj;
+ /**
+ * Constructor to initialize the adjacency list.
+ */
AdjacencyList() {
- adj = new LinkedHashMap>();
+ adj = new LinkedHashMap<>();
}
/**
- * This function adds an Edge to the adjaceny list
+ * Adds a directed edge from one vertex to another in the adjacency list.
+ * If the vertex does not exist, it will be added to the list.
*
- * @param from , the vertex the edge is from
- * @param to, the vertex the edge is going to
+ * @param from the starting vertex of the directed edge
+ * @param to the destination vertex of the directed edge
*/
void addEdge(E from, E to) {
- try {
- adj.get(from).add(to);
- } catch (Exception E) {
- adj.put(from, new ArrayList());
- adj.get(from).add(to);
+ if (!adj.containsKey(from)) {
+ adj.put(from, new ArrayList<>());
}
+ adj.get(from).add(to);
if (!adj.containsKey(to)) {
- adj.put(to, new ArrayList());
+ adj.put(to, new ArrayList<>());
}
}
/**
- * @param v, A vertex in a graph
- * @return returns an ArrayList of all the adjacents of vertex v
+ * Retrieves the list of adjacent vertices for a given vertex.
+ *
+ * @param v the vertex whose adjacent vertices are to be fetched
+ * @return an ArrayList of adjacent vertices for vertex v
*/
ArrayList getAdjacents(E v) {
return adj.get(v);
}
/**
- * @return returns a set of all vertices in the graph
+ * Retrieves the set of all vertices present in the graph.
+ *
+ * @return a set containing all vertices in the graph
*/
Set getVertices() {
return adj.keySet();
}
}
+/**
+ * A class that performs topological sorting on a directed graph using Kahn's algorithm.
+ *
+ * @param the type of vertices, extending Comparable to ensure that vertices
+ * can be compared
+ */
class TopologicalSort> {
AdjacencyList graph;
Map inDegree;
+ /**
+ * Constructor to initialize the topological sorting class with a given graph.
+ *
+ * @param graph the directed graph represented as an adjacency list
+ */
TopologicalSort(AdjacencyList graph) {
this.graph = graph;
}
/**
- * Calculates the in degree of all vertices
+ * Calculates the in-degree of all vertices in the graph. The in-degree is
+ * the number of edges directed into a vertex.
*/
void calculateInDegree() {
inDegree = new HashMap<>();
for (E vertex : graph.getVertices()) {
- if (!inDegree.containsKey(vertex)) {
- inDegree.put(vertex, 0);
- }
+ inDegree.putIfAbsent(vertex, 0);
for (E adjacent : graph.getAdjacents(vertex)) {
- try {
- inDegree.put(adjacent, inDegree.get(adjacent) + 1);
- } catch (Exception e) {
- inDegree.put(adjacent, 1);
- }
+ inDegree.put(adjacent, inDegree.getOrDefault(adjacent, 0) + 1);
}
}
}
/**
- * Returns an ArrayList with vertices arranged in topological order
+ * Returns an ArrayList containing the vertices of the graph arranged in
+ * topological order. Topological sorting ensures that for any directed edge
+ * (u, v), vertex u appears before vertex v in the ordering.
+ *
+ * @return an ArrayList of vertices in topological order
+ * @throws IllegalStateException if the graph contains a cycle
*/
ArrayList topSortOrder() {
calculateInDegree();
- Queue q = new LinkedList();
+ Queue q = new LinkedList<>();
- for (final var entry : inDegree.entrySet()) {
+ for (var entry : inDegree.entrySet()) {
if (entry.getValue() == 0) {
q.add(entry.getKey());
}
}
ArrayList answer = new ArrayList<>();
+ int processedVertices = 0;
while (!q.isEmpty()) {
E current = q.poll();
answer.add(current);
+ processedVertices++;
+
for (E adjacent : graph.getAdjacents(current)) {
inDegree.put(adjacent, inDegree.get(adjacent) - 1);
if (inDegree.get(adjacent) == 0) {
@@ -107,12 +131,16 @@ ArrayList topSortOrder() {
}
}
+ if (processedVertices != graph.getVertices().size()) {
+ throw new IllegalStateException("Graph contains a cycle, topological sort not possible");
+ }
+
return answer;
}
}
/**
- * A driver class that sorts a given graph in topological order.
+ * A driver class that sorts a given graph in topological order using Kahn's algorithm.
*/
public final class KahnsAlgorithm {
private KahnsAlgorithm() {
@@ -130,7 +158,7 @@ public static void main(String[] args) {
TopologicalSort topSort = new TopologicalSort<>(graph);
- // Printing the order
+ // Printing the topological order
for (String s : topSort.topSortOrder()) {
System.out.print(s + " ");
}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java b/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java
index c5f15839f997..78a184f042b5 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java
@@ -5,82 +5,91 @@
import java.util.Stack;
/**
- * Java program that implements Kosaraju Algorithm.
- * @author Shivanagouda S A
+ * This class implements the Kosaraju Algorithm to find all the Strongly Connected Components (SCCs)
+ * of a directed graph. Kosaraju's algorithm runs in linear time and leverages the concept that
+ * the SCCs of a directed graph remain the same in its transpose (reverse) graph.
+ *
*
- * Kosaraju algorithm is a linear time algorithm to find the strongly connected components of a
-directed graph, which, from here onwards will be referred by SCC. It leverages the fact that the
-transpose graph (same graph with all the edges reversed) has exactly the same SCCs as the original
-graph.
-
- * A graph is said to be strongly connected if every vertex is reachable from every other vertex.
-The SCCs of a directed graph form a partition into subgraphs that are themselves strongly
-connected. Single node is always a SCC.
-
- * Example:
-
-0 <--- 2 -------> 3 -------- > 4 ---- > 7
-| ^ | ^ ^
-| / | \ /
-| / | \ /
-v / v \ /
-1 5 --> 6
-
-For the above graph, the SCC list goes as follows:
-0, 1, 2
-3
-4, 5, 6
-7
-
-We can also see that order of the nodes in an SCC doesn't matter since they are in cycle.
-
-{@summary}
- * Kosaraju Algorithm:
-1. Perform DFS traversal of the graph. Push node to stack before returning. This gives edges
-sorted by lowest finish time.
-2. Find the transpose graph by reversing the edges.
-3. Pop nodes one by one from the stack and again to DFS on the modified graph.
-
-The transpose graph of the above graph:
-0 ---> 2 <------- 3 <------- 4 <------ 7
-^ / ^ \ /
-| / | \ /
-| / | \ /
-| v | v v
-1 5 <--- 6
-
-We can observe that this graph has the same SCC as that of original graph.
-
+ * A strongly connected component (SCC) of a directed graph is a subgraph where every vertex
+ * is reachable from every other vertex in the subgraph. The Kosaraju algorithm is particularly
+ * efficient for finding SCCs because it performs two Depth First Search (DFS) passes on the
+ * graph and its transpose.
+ *
+ *
+ *