From 95116dbee417bc83a16d274ba13ebd941980b122 Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Tue, 15 Jul 2025 08:23:49 +0300
Subject: [PATCH 01/57] refactor: improving `MedianOfMatrix` (#6376)
refactor: improving MedianOfMatrix
---
.../thealgorithms/matrix/MedianOfMatrix.java | 20 +++++++++----------
.../matrix/MedianOfMatrixTest.java | 16 +++++++++++++++
2 files changed, 26 insertions(+), 10 deletions(-)
diff --git a/src/main/java/com/thealgorithms/matrix/MedianOfMatrix.java b/src/main/java/com/thealgorithms/matrix/MedianOfMatrix.java
index c710c60a2d2a..1ec977af07c6 100644
--- a/src/main/java/com/thealgorithms/matrix/MedianOfMatrix.java
+++ b/src/main/java/com/thealgorithms/matrix/MedianOfMatrix.java
@@ -14,19 +14,19 @@ private MedianOfMatrix() {
}
public static int median(Iterable> matrix) {
- // Flatten the matrix into a 1D list
- List linear = new ArrayList<>();
+ List flattened = new ArrayList<>();
+
for (List row : matrix) {
- linear.addAll(row);
+ if (row != null) {
+ flattened.addAll(row);
+ }
}
- // Sort the 1D list
- Collections.sort(linear);
-
- // Calculate the middle index
- int mid = (0 + linear.size() - 1) / 2;
+ if (flattened.isEmpty()) {
+ throw new IllegalArgumentException("Matrix must contain at least one element.");
+ }
- // Return the median
- return linear.get(mid);
+ Collections.sort(flattened);
+ return flattened.get((flattened.size() - 1) / 2);
}
}
diff --git a/src/test/java/com/thealgorithms/matrix/MedianOfMatrixTest.java b/src/test/java/com/thealgorithms/matrix/MedianOfMatrixTest.java
index db66bb2d187b..b9b97014f3fc 100644
--- a/src/test/java/com/thealgorithms/matrix/MedianOfMatrixTest.java
+++ b/src/test/java/com/thealgorithms/matrix/MedianOfMatrixTest.java
@@ -1,9 +1,11 @@
package com.thealgorithms.matrix;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
@@ -31,4 +33,18 @@ public void testMedianWithEvenNumberOfElements() {
assertEquals(2, result);
}
+
+ @Test
+ public void testMedianSingleElement() {
+ List> matrix = new ArrayList<>();
+ matrix.add(List.of(1));
+
+ assertEquals(1, MedianOfMatrix.median(matrix));
+ }
+
+ @Test
+ void testEmptyMatrixThrowsException() {
+ Iterable> emptyMatrix = Collections.emptyList();
+ assertThrows(IllegalArgumentException.class, () -> MedianOfMatrix.median(emptyMatrix), "Expected median() to throw, but it didn't");
+ }
}
From 7e37d94c538dc376be8cd3672ca6711bba75cc1e Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Tue, 15 Jul 2025 08:26:49 +0300
Subject: [PATCH 02/57] refactor: improving readability
`DecimalToAnyUsingStack` (#6377)
refactor: improving readability DecimalToAnyUsingStack
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../SubsetSumSpaceOptimized.java | 48 ++++++++++---------
.../stacks/DecimalToAnyUsingStack.java | 29 +++++++----
2 files changed, 46 insertions(+), 31 deletions(-)
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimized.java b/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimized.java
index 946da46cb292..5c0167977ae4 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimized.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimized.java
@@ -1,35 +1,39 @@
package com.thealgorithms.dynamicprogramming;
-/*
-The Sum of Subset problem determines whether a subset of elements from a
-given array sums up to a specific target value.
-*/
+
+/**
+ * Utility class for solving the Subset Sum problem using a space-optimized dynamic programming approach.
+ *
+ *
This algorithm determines whether any subset of a given array sums up to a specific target value.
+ *
+ *
Time Complexity: O(n * sum)
+ *
Space Complexity: O(sum)
+ */
public final class SubsetSumSpaceOptimized {
private SubsetSumSpaceOptimized() {
}
+
/**
- * This method checks whether the subset of an array
- * contains a given sum or not. This is an space
- * optimized solution using 1D boolean array
- * Time Complexity: O(n * sum), Space complexity: O(sum)
+ * Determines whether there exists a subset of the given array that adds up to the specified sum.
+ * This method uses a space-optimized dynamic programming approach with a 1D boolean array.
*
- * @param arr An array containing integers
- * @param sum The target sum of the subset
- * @return True or False
+ * @param nums The array of non-negative integers
+ * @param targetSum The desired subset sum
+ * @return {@code true} if such a subset exists, {@code false} otherwise
*/
- public static boolean isSubsetSum(int[] arr, int sum) {
- int n = arr.length;
- // Declare the boolean array with size sum + 1
- boolean[] dp = new boolean[sum + 1];
+ public static boolean isSubsetSum(int[] nums, int targetSum) {
+ if (targetSum < 0) {
+ return false; // Subset sum can't be negative
+ }
- // Initialize the first element as true
- dp[0] = true;
+ boolean[] dp = new boolean[targetSum + 1];
+ dp[0] = true; // Empty subset always sums to 0
- // Find the subset sum using 1D array
- for (int i = 0; i < n; i++) {
- for (int j = sum; j >= arr[i]; j--) {
- dp[j] = dp[j] || dp[j - arr[i]];
+ for (int number : nums) {
+ for (int j = targetSum; j >= number; j--) {
+ dp[j] = dp[j] || dp[j - number];
}
}
- return dp[sum];
+
+ return dp[targetSum];
}
}
diff --git a/src/main/java/com/thealgorithms/stacks/DecimalToAnyUsingStack.java b/src/main/java/com/thealgorithms/stacks/DecimalToAnyUsingStack.java
index ff6402c92695..2852454fd096 100644
--- a/src/main/java/com/thealgorithms/stacks/DecimalToAnyUsingStack.java
+++ b/src/main/java/com/thealgorithms/stacks/DecimalToAnyUsingStack.java
@@ -2,17 +2,29 @@
import java.util.Stack;
+/**
+ * Utility class for converting a non-negative decimal (base-10) integer
+ * to its representation in another radix (base) between 2 and 16, inclusive.
+ *
+ *
This class uses a stack-based approach to reverse the digits obtained from
+ * successive divisions by the target radix.
+ *
+ *
This class cannot be instantiated.
+ */
public final class DecimalToAnyUsingStack {
+
private DecimalToAnyUsingStack() {
}
+ private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
/**
* Convert a decimal number to another radix.
*
* @param number the number to be converted
* @param radix the radix
* @return the number represented in the new radix as a String
- * @throws IllegalArgumentException if number is negative or radix is not between 2 and 16 inclusive
+ * @throws IllegalArgumentException if number is negative or radix is not between 2 and 16 inclusive
*/
public static String convert(int number, int radix) {
if (number < 0) {
@@ -26,18 +38,17 @@ public static String convert(int number, int radix) {
return "0";
}
- char[] tables = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
-
- Stack bits = new Stack<>();
+ Stack digitStack = new Stack<>();
while (number > 0) {
- bits.push(tables[number % radix]);
- number = number / radix;
+ digitStack.push(DIGITS[number % radix]);
+ number /= radix;
}
- StringBuilder result = new StringBuilder();
- while (!bits.isEmpty()) {
- result.append(bits.pop());
+ StringBuilder result = new StringBuilder(digitStack.size());
+ while (!digitStack.isEmpty()) {
+ result.append(digitStack.pop());
}
+
return result.toString();
}
}
From ca7c77f16bf59a338adb5e41486387c982b38387 Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Tue, 15 Jul 2025 08:31:09 +0300
Subject: [PATCH 03/57] refactor: improving `DisjointSetUnion` (#6378)
* refactor: improving DisjointSetUnion
* refactor: remove comment as it already in description
---------
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../disjointsetunion/DisjointSetUnion.java | 64 +++++++++++--------
1 file changed, 38 insertions(+), 26 deletions(-)
diff --git a/src/main/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnion.java b/src/main/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnion.java
index 583800998c81..55951be82c8a 100644
--- a/src/main/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnion.java
+++ b/src/main/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnion.java
@@ -1,53 +1,65 @@
package com.thealgorithms.datastructures.disjointsetunion;
/**
- * Disjoint Set Union or DSU is useful for solving problems related to connected components,
- * cycle detection in graphs, and maintaining relationships in disjoint sets of data.
- * It is commonly employed in graph algorithms and problems.
+ * Disjoint Set Union (DSU), also known as Union-Find, is a data structure that tracks a set of elements
+ * partitioned into disjoint (non-overlapping) subsets. It supports two primary operations efficiently:
*
- * @see Disjoint Set Union
+ *
+ *
Find: Determine which subset a particular element belongs to.
+ *
Union: Merge two subsets into a single subset.
+ *
+ *
+ * @see Disjoint Set Union (Wikipedia)
*/
public class DisjointSetUnion {
/**
- * Creates a new node of DSU with parent initialised as same node
+ * Creates a new disjoint set containing the single specified element.
+ *
+ * @param value the element to be placed in a new singleton set
+ * @return a node representing the new set
*/
- public Node makeSet(final T x) {
- return new Node(x);
+ public Node makeSet(final T value) {
+ return new Node<>(value);
}
/**
- * Finds and returns the representative (root) element of the set to which a given element belongs.
- * This operation uses path compression to optimize future findSet operations.
+ * Finds and returns the representative (root) of the set containing the given node.
+ * This method applies path compression to flatten the tree structure for future efficiency.
+ *
+ * @param node the node whose set representative is to be found
+ * @return the representative (root) node of the set
*/
public Node findSet(Node node) {
- while (node != node.parent) {
- node = node.parent;
+ if (node != node.parent) {
+ node.parent = findSet(node.parent);
}
- return node;
+ return node.parent;
}
/**
- * Unions two sets by merging their representative elements. The merge is performed based on the rank of each set
- * to ensure efficient merging and path compression to optimize future findSet operations.
+ * Merges the sets containing the two given nodes. Union by rank is used to attach the smaller tree under the larger one.
+ * If both sets have the same rank, one becomes the parent and its rank is incremented.
+ *
+ * @param x a node in the first set
+ * @param y a node in the second set
*/
- public void unionSets(final Node x, final Node y) {
- Node nx = findSet(x);
- Node ny = findSet(y);
+ public void unionSets(Node x, Node y) {
+ Node rootX = findSet(x);
+ Node rootY = findSet(y);
- if (nx == ny) {
- return; // Both elements already belong to the same set.
+ if (rootX == rootY) {
+ return; // They are already in the same set
}
// Merging happens based on rank of node, this is done to avoid long chaining of nodes and reduce time
// to find root of the component. Idea is to attach small components in big, instead of other way around.
- if (nx.rank > ny.rank) {
- ny.parent = nx;
- } else if (ny.rank > nx.rank) {
- nx.parent = ny;
+ if (rootX.rank > rootY.rank) {
+ rootY.parent = rootX;
+ } else if (rootY.rank > rootX.rank) {
+ rootX.parent = rootY;
} else {
- // Both sets have the same rank; choose one as the parent and increment the rank.
- ny.parent = nx;
- nx.rank++;
+ rootY.parent = rootX;
+ rootX.rank++;
}
}
}
From 287a708c7fec2d60455bd786b7b258028532ed2e Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Tue, 15 Jul 2025 18:04:58 +0300
Subject: [PATCH 04/57] refactor: `Intersection` (#6379)
refactor: Intersection improvement
---
.../hashmap/hashing/Intersection.java | 58 ++++++++++---------
1 file changed, 32 insertions(+), 26 deletions(-)
diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Intersection.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Intersection.java
index 0e49218d6348..5760d39f1df7 100644
--- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Intersection.java
+++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Intersection.java
@@ -8,60 +8,66 @@
/**
* The {@code Intersection} class provides a method to compute the intersection of two integer arrays.
- * The intersection is defined as the set of common elements present in both arrays.
*
- * This class utilizes a HashMap to efficiently count occurrences of elements in the first array,
- * allowing for an efficient lookup of common elements in the second array.
+ * This intersection includes duplicate values — meaning elements are included in the result
+ * as many times as they appear in both arrays (i.e., multiset intersection).
*
*
*
- * Example:
- *
+ * The algorithm uses a {@link java.util.HashMap} to count occurrences of elements in the first array,
+ * then iterates through the second array to collect common elements based on these counts.
+ *
+ *
+ *
+ * Example usage:
+ *
{@code
* int[] array1 = {1, 2, 2, 1};
* int[] array2 = {2, 2};
- * List result = Intersection.intersection(array1, array2); // result will contain [2, 2]
- *
+ * List result = Intersection.intersection(array1, array2); // result: [2, 2]
+ * }
*
*
*
- * Note: The order of the returned list may vary since it depends on the order of elements
- * in the input arrays.
+ * Note: The order of elements in the returned list depends on the order in the second input array.
*
*/
public final class Intersection {
+ private Intersection() {
+ // Utility class; prevent instantiation
+ }
+
/**
- * Computes the intersection of two integer arrays.
+ * Computes the intersection of two integer arrays, preserving element frequency.
+ * For example, given [1,2,2,3] and [2,2,4], the result will be [2,2].
+ *
* Steps:
- * 1. Count the occurrences of each element in the first array using a HashMap.
- * 2. Iterate over the second array and check if the element is present in the HashMap.
- * If it is, add it to the result list and decrement the count in the HashMap.
- * 3. Return the result list containing the intersection of the two arrays.
+ * 1. Count the occurrences of each element in the first array using a map.
+ * 2. Iterate over the second array and collect common elements.
*
* @param arr1 the first array of integers
* @param arr2 the second array of integers
- * @return a list containing the intersection of the two arrays, or an empty list if either array is null or empty
+ * @return a list containing the intersection of the two arrays (with duplicates),
+ * or an empty list if either array is null or empty
*/
public static List intersection(int[] arr1, int[] arr2) {
if (arr1 == null || arr2 == null || arr1.length == 0 || arr2.length == 0) {
return Collections.emptyList();
}
- Map cnt = new HashMap<>(16);
- for (int v : arr1) {
- cnt.put(v, cnt.getOrDefault(v, 0) + 1);
+ Map countMap = new HashMap<>();
+ for (int num : arr1) {
+ countMap.put(num, countMap.getOrDefault(num, 0) + 1);
}
- List res = new ArrayList<>();
- for (int v : arr2) {
- if (cnt.containsKey(v) && cnt.get(v) > 0) {
- res.add(v);
- cnt.put(v, cnt.get(v) - 1);
+ List result = new ArrayList<>();
+ for (int num : arr2) {
+ if (countMap.getOrDefault(num, 0) > 0) {
+ result.add(num);
+ countMap.computeIfPresent(num, (k, v) -> v - 1);
}
}
- return res;
- }
- private Intersection() {
+ return result;
}
}
From dcb02c61dfadd6cab986c44c2999aec7fea7076a Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Tue, 15 Jul 2025 18:10:06 +0300
Subject: [PATCH 05/57] refactor: `MajorityElement` (#6380)
* refactor: MajorityElement
* refactor: fix import ordering
---------
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../hashmap/hashing/MajorityElement.java | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElement.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElement.java
index 915e4228b618..5fd6b2e7f3cb 100644
--- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElement.java
+++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElement.java
@@ -1,8 +1,10 @@
package com.thealgorithms.datastructures.hashmap.hashing;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* This class provides a method to find the majority element(s) in an array of integers.
@@ -18,13 +20,18 @@ private MajorityElement() {
* Returns a list of majority element(s) from the given array of integers.
*
* @param nums an array of integers
- * @return a list containing the majority element(s); returns an empty list if none exist
+ * @return a list containing the majority element(s); returns an empty list if none exist or input is null/empty
*/
public static List majority(int[] nums) {
- HashMap numToCount = new HashMap<>();
+ if (nums == null || nums.length == 0) {
+ return Collections.emptyList();
+ }
+
+ Map numToCount = new HashMap<>();
for (final var num : nums) {
numToCount.merge(num, 1, Integer::sum);
}
+
List majorityElements = new ArrayList<>();
for (final var entry : numToCount.entrySet()) {
if (entry.getValue() >= nums.length / 2) {
From d55e89dc71284cefe0e75bde8c9ecaa2496deac7 Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Wed, 16 Jul 2025 18:02:16 +0300
Subject: [PATCH 06/57] refactor: `Mode` (#6381)
refactor: Mode
---
.../java/com/thealgorithms/maths/Mode.java | 37 ++++++++++---------
1 file changed, 20 insertions(+), 17 deletions(-)
diff --git a/src/main/java/com/thealgorithms/maths/Mode.java b/src/main/java/com/thealgorithms/maths/Mode.java
index f0b747cf02ec..657f8ece2a50 100644
--- a/src/main/java/com/thealgorithms/maths/Mode.java
+++ b/src/main/java/com/thealgorithms/maths/Mode.java
@@ -3,46 +3,49 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
-/*
- * Find the mode of an array of numbers
- *
- * The mode of an array of numbers is the most frequently occurring number in the array,
- * or the most frequently occurring numbers if there are multiple numbers with the same frequency
+/**
+ * Utility class to calculate the mode(s) of an array of integers.
+ *
+ * The mode of an array is the integer value(s) that occur most frequently.
+ * If multiple values have the same highest frequency, all such values are returned.
+ *
*/
public final class Mode {
private Mode() {
}
- /*
- * Find the mode of an array of integers
+ /**
+ * Computes the mode(s) of the specified array of integers.
+ *
+ * If the input array is empty, this method returns {@code null}.
+ * If multiple numbers share the highest frequency, all are returned in the result array.
+ *
*
- * @param numbers array of integers
- * @return mode of the array
+ * @param numbers an array of integers to analyze
+ * @return an array containing the mode(s) of the input array, or {@code null} if the input is empty
*/
public static int[] mode(final int[] numbers) {
if (numbers.length == 0) {
return null;
}
- HashMap count = new HashMap<>();
+ Map count = new HashMap<>();
for (int num : numbers) {
- if (count.containsKey(num)) {
- count.put(num, count.get(num) + 1);
- } else {
- count.put(num, 1);
- }
+ count.put(num, count.getOrDefault(num, 0) + 1);
}
int max = Collections.max(count.values());
- ArrayList modes = new ArrayList<>();
+ List modes = new ArrayList<>();
for (final var entry : count.entrySet()) {
if (entry.getValue() == max) {
modes.add(entry.getKey());
}
}
- return modes.stream().mapToInt(n -> n).toArray();
+ return modes.stream().mapToInt(Integer::intValue).toArray();
}
}
From 434ab50ff4d34c50e19a39f4a4d1f3a4df459e22 Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Wed, 16 Jul 2025 18:05:13 +0300
Subject: [PATCH 07/57] refactor: `Convolution` (#6382)
refactor: Convolution
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../com/thealgorithms/maths/Convolution.java | 25 ++++++++-----------
1 file changed, 11 insertions(+), 14 deletions(-)
diff --git a/src/main/java/com/thealgorithms/maths/Convolution.java b/src/main/java/com/thealgorithms/maths/Convolution.java
index 93e103f8c7cf..a86ecabce933 100644
--- a/src/main/java/com/thealgorithms/maths/Convolution.java
+++ b/src/main/java/com/thealgorithms/maths/Convolution.java
@@ -23,24 +23,21 @@ public static double[] convolution(double[] a, double[] b) {
double[] convolved = new double[a.length + b.length - 1];
/*
- The discrete convolution of two signals A and B is defined as:
-
- A.length
- C[i] = Σ (A[k]*B[i-k])
- k=0
-
- It's obvious that: 0 <= k <= A.length , 0 <= i <= A.length + B.length - 2 and 0 <= i-k <=
- B.length - 1 From the last inequality we get that: i - B.length + 1 <= k <= i and thus we get
- the conditions below.
+ * Discrete convolution formula:
+ * C[i] = Σ A[k] * B[i - k]
+ * where k ranges over valid indices so that both A[k] and B[i-k] are in bounds.
*/
+
for (int i = 0; i < convolved.length; i++) {
- convolved[i] = 0;
- int k = Math.max(i - b.length + 1, 0);
+ double sum = 0;
+ int kStart = Math.max(0, i - b.length + 1);
+ int kEnd = Math.min(i, a.length - 1);
- while (k < i + 1 && k < a.length) {
- convolved[i] += a[k] * b[i - k];
- k++;
+ for (int k = kStart; k <= kEnd; k++) {
+ sum += a[k] * b[i - k];
}
+
+ convolved[i] = sum;
}
return convolved;
From dd1a51b20f15eac49365d154b479c5848021c8f9 Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Wed, 16 Jul 2025 18:10:22 +0300
Subject: [PATCH 08/57] testing: add more cases for `AverageTest` (#6384)
* testing: add more cases for AverageTest
* checkstyle: fix formatting
---
.../com/thealgorithms/maths/AverageTest.java | 45 ++++++++++++-------
1 file changed, 30 insertions(+), 15 deletions(-)
diff --git a/src/test/java/com/thealgorithms/maths/AverageTest.java b/src/test/java/com/thealgorithms/maths/AverageTest.java
index c5c751938f5d..638739bc4fda 100644
--- a/src/test/java/com/thealgorithms/maths/AverageTest.java
+++ b/src/test/java/com/thealgorithms/maths/AverageTest.java
@@ -1,33 +1,48 @@
package com.thealgorithms.maths;
-import org.junit.jupiter.api.Assertions;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
public class AverageTest {
private static final double SMALL_VALUE = 0.00001d;
- @Test
- public void testAverageDouble12() {
- double[] numbers = {3d, 6d, 9d, 12d, 15d, 18d, 21d};
- Assertions.assertEquals(12d, Average.average(numbers), SMALL_VALUE);
+ @ParameterizedTest(name = "average({0}) should be approximately {1}")
+ @MethodSource("provideDoubleArrays")
+ void testAverageDouble(double[] numbers, double expected) {
+ assertEquals(expected, Average.average(numbers), SMALL_VALUE);
}
- @Test
- public void testAverageDouble20() {
- double[] numbers = {5d, 10d, 15d, 20d, 25d, 30d, 35d};
- Assertions.assertEquals(20d, Average.average(numbers), SMALL_VALUE);
+ @ParameterizedTest(name = "average({0}) should be {1}")
+ @MethodSource("provideIntArrays")
+ void testAverageInt(int[] numbers, long expected) {
+ assertEquals(expected, Average.average(numbers));
}
@Test
- public void testAverageDouble() {
- double[] numbers = {1d, 2d, 3d, 4d, 5d, 6d, 7d, 8d};
- Assertions.assertEquals(4.5d, Average.average(numbers), SMALL_VALUE);
+ void testAverageDoubleThrowsExceptionOnNullOrEmpty() {
+ assertThrows(IllegalArgumentException.class, () -> Average.average((double[]) null));
+ assertThrows(IllegalArgumentException.class, () -> Average.average(new double[0]));
}
@Test
- public void testAverageInt() {
- int[] numbers = {2, 4, 10};
- Assertions.assertEquals(5, Average.average(numbers));
+ void testAverageIntThrowsExceptionOnNullOrEmpty() {
+ assertThrows(IllegalArgumentException.class, () -> Average.average((int[]) null));
+ assertThrows(IllegalArgumentException.class, () -> Average.average(new int[0]));
+ }
+
+ private static Stream provideDoubleArrays() {
+ return Stream.of(Arguments.of(new double[] {3d, 6d, 9d, 12d, 15d, 18d, 21d}, 12d), Arguments.of(new double[] {5d, 10d, 15d, 20d, 25d, 30d, 35d}, 20d), Arguments.of(new double[] {1d, 2d, 3d, 4d, 5d, 6d, 7d, 8d}, 4.5d), Arguments.of(new double[] {0d, 0d, 0d}, 0d),
+ Arguments.of(new double[] {-1d, -2d, -3d}, -2d), Arguments.of(new double[] {1e-10, 1e-10, 1e-10}, 1e-10));
+ }
+
+ private static Stream provideIntArrays() {
+ return Stream.of(Arguments.of(new int[] {2, 4, 10}, 5L), Arguments.of(new int[] {0, 0, 0}, 0L), Arguments.of(new int[] {-1, -2, -3}, -2L), Arguments.of(new int[] {1, 1, 1, 1, 1}, 1L));
}
}
From 440b6f5edf86ae8fbf15ff28990f4b62ae46be9c Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Thu, 17 Jul 2025 18:29:45 +0300
Subject: [PATCH 09/57] testing: improve tests coverage `AbsoluteValueTest`
(#6385)
testing: improve tests coverage AbsoluteValueTest
---
.../maths/AbsoluteValueTest.java | 24 +++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/src/test/java/com/thealgorithms/maths/AbsoluteValueTest.java b/src/test/java/com/thealgorithms/maths/AbsoluteValueTest.java
index f87652253641..907d5cb45ef9 100644
--- a/src/test/java/com/thealgorithms/maths/AbsoluteValueTest.java
+++ b/src/test/java/com/thealgorithms/maths/AbsoluteValueTest.java
@@ -12,4 +12,28 @@ public class AbsoluteValueTest {
void testGetAbsValue() {
Stream.generate(() -> ThreadLocalRandom.current().nextInt()).limit(1000).forEach(number -> assertEquals(Math.abs(number), AbsoluteValue.getAbsValue(number)));
}
+
+ @Test
+ void testZero() {
+ assertEquals(0, AbsoluteValue.getAbsValue(0));
+ }
+
+ @Test
+ void testPositiveNumbers() {
+ assertEquals(5, AbsoluteValue.getAbsValue(5));
+ assertEquals(123456, AbsoluteValue.getAbsValue(123456));
+ assertEquals(Integer.MAX_VALUE, AbsoluteValue.getAbsValue(Integer.MAX_VALUE));
+ }
+
+ @Test
+ void testNegativeNumbers() {
+ assertEquals(5, AbsoluteValue.getAbsValue(-5));
+ assertEquals(123456, AbsoluteValue.getAbsValue(-123456));
+ assertEquals(Integer.MAX_VALUE, AbsoluteValue.getAbsValue(-Integer.MAX_VALUE));
+ }
+
+ @Test
+ void testMinIntEdgeCase() {
+ assertEquals(Integer.MIN_VALUE, AbsoluteValue.getAbsValue(Integer.MIN_VALUE));
+ }
}
From a796f6dc413003b14163787883c0398e0af9e0ac Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Thu, 17 Jul 2025 18:32:48 +0300
Subject: [PATCH 10/57] testing: added unit tests for the `BinaryPow.binPow`
(#6386)
testing: added unit tests for the BinaryPow.binPow
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../thealgorithms/maths/BinaryPowTest.java | 30 +++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/src/test/java/com/thealgorithms/maths/BinaryPowTest.java b/src/test/java/com/thealgorithms/maths/BinaryPowTest.java
index f9b019e8fad4..632dfbd1d65e 100644
--- a/src/test/java/com/thealgorithms/maths/BinaryPowTest.java
+++ b/src/test/java/com/thealgorithms/maths/BinaryPowTest.java
@@ -13,4 +13,34 @@ void testBinPow() {
assertEquals(729, BinaryPow.binPow(9, 3));
assertEquals(262144, BinaryPow.binPow(8, 6));
}
+
+ @Test
+ void testZeroExponent() {
+ assertEquals(1, BinaryPow.binPow(2, 0));
+ assertEquals(1, BinaryPow.binPow(100, 0));
+ assertEquals(1, BinaryPow.binPow(-5, 0));
+ }
+
+ @Test
+ void testZeroBase() {
+ assertEquals(0, BinaryPow.binPow(0, 5));
+ assertEquals(1, BinaryPow.binPow(0, 0));
+ }
+
+ @Test
+ void testOneBase() {
+ assertEquals(1, BinaryPow.binPow(1, 100));
+ assertEquals(1, BinaryPow.binPow(1, 0));
+ }
+
+ @Test
+ void testNegativeBase() {
+ assertEquals(-8, BinaryPow.binPow(-2, 3));
+ assertEquals(16, BinaryPow.binPow(-2, 4));
+ }
+
+ @Test
+ void testLargeExponent() {
+ assertEquals(1073741824, BinaryPow.binPow(2, 30));
+ }
}
From 054002adb20c5c37907970543b4c3e6d390e6fbf Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Thu, 17 Jul 2025 18:37:46 +0300
Subject: [PATCH 11/57] testing: added unit tests for the
`MinStackUsingTwoStacks` (#6387)
* testing: added unit tests for the MinStackUsingTwoStacks
* checkstyle: fix import order
---------
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../stacks/MinStackUsingTwoStacksTest.java | 88 +++++++++++++++++--
1 file changed, 81 insertions(+), 7 deletions(-)
diff --git a/src/test/java/com/thealgorithms/stacks/MinStackUsingTwoStacksTest.java b/src/test/java/com/thealgorithms/stacks/MinStackUsingTwoStacksTest.java
index e5deb17e9a8f..36bdde49b235 100644
--- a/src/test/java/com/thealgorithms/stacks/MinStackUsingTwoStacksTest.java
+++ b/src/test/java/com/thealgorithms/stacks/MinStackUsingTwoStacksTest.java
@@ -1,38 +1,112 @@
package com.thealgorithms.stacks;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.util.EmptyStackException;
import org.junit.jupiter.api.Test;
public class MinStackUsingTwoStacksTest {
@Test
- public void testMinStackOperations() {
+ public void testBasicOperations() {
MinStackUsingTwoStacks minStack = new MinStackUsingTwoStacks();
minStack.push(3);
minStack.push(5);
- assertEquals(3, minStack.getMin());
+ assertEquals(3, minStack.getMin(), "Min should be 3");
minStack.push(2);
minStack.push(1);
- assertEquals(1, minStack.getMin());
+ assertEquals(1, minStack.getMin(), "Min should be 1");
minStack.pop();
- assertEquals(2, minStack.getMin());
+ assertEquals(2, minStack.getMin(), "Min should be 2 after popping 1");
+
+ assertEquals(2, minStack.top(), "Top should be 2");
+ }
+
+ @Test
+ public void testPushDuplicateMins() {
+ MinStackUsingTwoStacks minStack = new MinStackUsingTwoStacks();
+ minStack.push(2);
+ minStack.push(2);
+ minStack.push(1);
+ minStack.push(1);
+ assertEquals(1, minStack.getMin(), "Min should be 1");
+
+ minStack.pop();
+ assertEquals(1, minStack.getMin(), "Min should still be 1 after popping one 1");
+
+ minStack.pop();
+ assertEquals(2, minStack.getMin(), "Min should be 2 after popping both 1s");
+
+ minStack.pop();
+ assertEquals(2, minStack.getMin(), "Min should still be 2 after popping one 2");
+
+ minStack.pop();
+ // Now stack is empty, expect exception on getMin
+ assertThrows(EmptyStackException.class, minStack::getMin);
}
@Test
- public void testMinStackOperations2() {
+ public void testPopOnEmptyStack() {
MinStackUsingTwoStacks minStack = new MinStackUsingTwoStacks();
+ assertThrows(EmptyStackException.class, minStack::pop);
+ }
+
+ @Test
+ public void testTopOnEmptyStack() {
+ MinStackUsingTwoStacks minStack = new MinStackUsingTwoStacks();
+ assertThrows(EmptyStackException.class, minStack::top);
+ }
+
+ @Test
+ public void testGetMinOnEmptyStack() {
+ MinStackUsingTwoStacks minStack = new MinStackUsingTwoStacks();
+ assertThrows(EmptyStackException.class, minStack::getMin);
+ }
+
+ @Test
+ public void testSingleElementStack() {
+ MinStackUsingTwoStacks minStack = new MinStackUsingTwoStacks();
+ minStack.push(10);
+ assertEquals(10, minStack.getMin());
+ assertEquals(10, minStack.top());
+
+ minStack.pop();
+ assertThrows(EmptyStackException.class, minStack::getMin);
+ }
+
+ @Test
+ public void testIncreasingSequence() {
+ MinStackUsingTwoStacks minStack = new MinStackUsingTwoStacks();
+ minStack.push(1);
+ minStack.push(2);
minStack.push(3);
- minStack.push(5);
- assertEquals(3, minStack.getMin());
+ minStack.push(4);
+
+ assertEquals(1, minStack.getMin());
+ assertEquals(4, minStack.top());
+
+ minStack.pop();
+ minStack.pop();
+ assertEquals(1, minStack.getMin());
+ assertEquals(2, minStack.top());
+ }
+ @Test
+ public void testDecreasingSequence() {
+ MinStackUsingTwoStacks minStack = new MinStackUsingTwoStacks();
+ minStack.push(4);
+ minStack.push(3);
minStack.push(2);
minStack.push(1);
+
assertEquals(1, minStack.getMin());
+ assertEquals(1, minStack.top());
minStack.pop();
assertEquals(2, minStack.getMin());
+ assertEquals(2, minStack.top());
}
}
From 7f6e677b071df752844a931bf9f732b174786b59 Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Thu, 17 Jul 2025 23:00:41 +0300
Subject: [PATCH 12/57] testing: improve test coverage `SortedLinkedListTest`
(#6388)
* testing: improve test coverage SortedLinkedListTest
* checkstyle: fix comments formatting
* checkstyle: fix formatting
---
.../lists/SortedLinkedListTest.java | 77 +++++++++++++++++++
1 file changed, 77 insertions(+)
diff --git a/src/test/java/com/thealgorithms/datastructures/lists/SortedLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/SortedLinkedListTest.java
index 82e0853da374..71f932465eef 100644
--- a/src/test/java/com/thealgorithms/datastructures/lists/SortedLinkedListTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/lists/SortedLinkedListTest.java
@@ -128,4 +128,81 @@ public void testIsEmptyAfterDeletion() {
list.delete(10);
assertTrue(list.isEmpty());
}
+
+ @Test
+ public void testInsertNegativeNumbers() {
+ list.insert(-10);
+ list.insert(-5);
+ list.insert(-20);
+ assertEquals("[-20, -10, -5]", list.toString());
+ }
+
+ @Test
+ public void testInsertMixedPositiveAndNegativeNumbers() {
+ list.insert(0);
+ list.insert(-1);
+ list.insert(1);
+ assertEquals("[-1, 0, 1]", list.toString());
+ }
+
+ @Test
+ public void testMultipleDeletesUntilEmpty() {
+ list.insert(2);
+ list.insert(4);
+ list.insert(6);
+ assertTrue(list.delete(4));
+ assertTrue(list.delete(2));
+ assertTrue(list.delete(6));
+ assertTrue(list.isEmpty());
+ assertEquals("[]", list.toString());
+ }
+
+ @Test
+ public void testDeleteDuplicateValuesOnlyDeletesOneInstance() {
+ list.insert(5);
+ list.insert(5);
+ list.insert(5);
+ assertTrue(list.delete(5));
+ assertEquals("[5, 5]", list.toString());
+ assertTrue(list.delete(5));
+ assertEquals("[5]", list.toString());
+ assertTrue(list.delete(5));
+ assertEquals("[]", list.toString());
+ }
+
+ @Test
+ public void testSearchOnListWithDuplicates() {
+ list.insert(7);
+ list.insert(7);
+ list.insert(7);
+ assertTrue(list.search(7));
+ assertFalse(list.search(10));
+ }
+
+ @Test
+ public void testToStringOnEmptyList() {
+ assertEquals("[]", list.toString());
+ }
+
+ @Test
+ public void testDeleteAllDuplicates() {
+ list.insert(4);
+ list.insert(4);
+ list.insert(4);
+ assertTrue(list.delete(4));
+ assertTrue(list.delete(4));
+ assertTrue(list.delete(4));
+ assertFalse(list.delete(4)); // nothing left to delete
+ assertEquals("[]", list.toString());
+ }
+
+ @Test
+ public void testInsertAfterDeletion() {
+ list.insert(1);
+ list.insert(3);
+ list.insert(5);
+ assertTrue(list.delete(3));
+ list.insert(2);
+ assertEquals("[1, 2, 5]", list.toString());
+ }
}
From 2f2a32b8c2c2b2e602f2744aa48cc33eaa3f692f Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Thu, 17 Jul 2025 23:03:24 +0300
Subject: [PATCH 13/57] testing: improve test coverage `ParityCheckTest`
(#6389)
testing: improve test coverage ParityCheckTest
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../bitmanipulation/ParityCheckTest.java | 26 ++++++++++++++++---
1 file changed, 23 insertions(+), 3 deletions(-)
diff --git a/src/test/java/com/thealgorithms/bitmanipulation/ParityCheckTest.java b/src/test/java/com/thealgorithms/bitmanipulation/ParityCheckTest.java
index 90147a61207b..1654c8ddfc1e 100644
--- a/src/test/java/com/thealgorithms/bitmanipulation/ParityCheckTest.java
+++ b/src/test/java/com/thealgorithms/bitmanipulation/ParityCheckTest.java
@@ -6,10 +6,30 @@
import org.junit.jupiter.api.Test;
public class ParityCheckTest {
+ @Test
+ public void testIsEvenParity() {
+ assertTrue(ParityCheck.checkParity(0)); // 0 -> 0 ones
+ assertTrue(ParityCheck.checkParity(3)); // 11 -> 2 ones
+ assertTrue(ParityCheck.checkParity(5)); // 101 -> 2 ones
+ assertTrue(ParityCheck.checkParity(10)); // 1010 -> 2 ones
+ assertTrue(ParityCheck.checkParity(15)); // 1111 -> 4 ones
+ assertTrue(ParityCheck.checkParity(1023)); // 10 ones
+ }
+
@Test
public void testIsOddParity() {
- assertTrue(ParityCheck.checkParity(5)); // 101 has 2 ones (even parity)
- assertFalse(ParityCheck.checkParity(7)); // 111 has 3 ones (odd parity)
- assertFalse(ParityCheck.checkParity(8)); // 1000 has 1 one (odd parity)
+ assertFalse(ParityCheck.checkParity(1)); // 1 -> 1 one
+ assertFalse(ParityCheck.checkParity(2)); // 10 -> 1 one
+ assertFalse(ParityCheck.checkParity(7)); // 111 -> 3 ones
+ assertFalse(ParityCheck.checkParity(8)); // 1000 -> 1 one
+ assertFalse(ParityCheck.checkParity(11)); // 1011 -> 3 ones
+ assertFalse(ParityCheck.checkParity(31)); // 11111 -> 5 ones
+ }
+
+ @Test
+ public void testLargeNumbers() {
+ assertTrue(ParityCheck.checkParity(0b10101010)); // 4 ones
+ assertFalse(ParityCheck.checkParity(0b100000000)); // 1 one
+ assertTrue(ParityCheck.checkParity(0xAAAAAAAA)); // Alternating bits, 16 ones
}
}
From d0d4b3c8fb432f50b03d55d2c62ba09c2f950790 Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Fri, 18 Jul 2025 08:51:11 +0300
Subject: [PATCH 14/57] testing: additional testcases for
`CountSinglyLinkedListRecursionTest` (#6392)
testing: additional testcases for CountSinglyLinkedListRecursionTest
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../CountSinglyLinkedListRecursionTest.java | 35 +++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java b/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java
index 1a3efe8a5572..1d814d0c2f9f 100644
--- a/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java
@@ -46,4 +46,39 @@ public void testCountWithDuplicateElements() {
list.insert(3);
assertEquals(5, list.count(), "Count of a list with duplicate elements should match total node count.");
}
+
+ @Test
+ public void testCountAfterClearingList() {
+ for (int i = 1; i <= 4; i++) {
+ list.insert(i);
+ }
+ list.clear(); // assuming you have a clear method; if not, skip this
+ assertEquals(0, list.count(), "Count after clearing the list should be 0.");
+ }
+
+ @Test
+ public void testCountOnVeryLargeList() {
+ int n = 1000;
+ for (int i = 0; i < n; i++) {
+ list.insert(i);
+ }
+ assertEquals(n, list.count(), "Count should correctly return for large list sizes.");
+ }
+
+ @Test
+ public void testCountOnListWithNegativeNumbers() {
+ list.insert(-1);
+ list.insert(-5);
+ list.insert(-10);
+ assertEquals(3, list.count(), "Count should correctly handle negative values.");
+ }
+
+ @Test
+ public void testCountIsConsistentWithoutModification() {
+ list.insert(1);
+ list.insert(2);
+ int firstCount = list.count();
+ int secondCount = list.count();
+ assertEquals(firstCount, secondCount, "Repeated count calls should return consistent values.");
+ }
}
From 44c572b36bd8d2d80fcd85fd296ea9fb5221306e Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 18 Jul 2025 13:58:17 +0300
Subject: [PATCH 15/57] chore(deps): bump com.mebigfatguy.fb-contrib:fb-contrib
from 7.6.11 to 7.6.12 (#6406)
chore(deps): bump com.mebigfatguy.fb-contrib:fb-contrib
Bumps [com.mebigfatguy.fb-contrib:fb-contrib](https://github.com/mebigfatguy/fb-contrib) from 7.6.11 to 7.6.12.
- [Commits](https://github.com/mebigfatguy/fb-contrib/commits/v7.6.12)
---
updated-dependencies:
- dependency-name: com.mebigfatguy.fb-contrib:fb-contrib
dependency-version: 7.6.12
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Oleksandr Klymenko
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index f5f1e0be12f7..f18462bec8fb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -128,7 +128,7 @@
com.mebigfatguy.fb-contribfb-contrib
- 7.6.11
+ 7.6.12com.h3xstream.findsecbugs
From fc477ee8da73656ae5ab5c4d481466951025580a Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Fri, 18 Jul 2025 22:29:16 +0300
Subject: [PATCH 16/57] testing: improving test coverage
`CountingInversionsTest` (#6393)
testing: improving test coverage CountingInversionsTest
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../CountingInversionsTest.java | 31 +++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/src/test/java/com/thealgorithms/divideandconquer/CountingInversionsTest.java b/src/test/java/com/thealgorithms/divideandconquer/CountingInversionsTest.java
index d12614d6fd06..f8356a87eb31 100644
--- a/src/test/java/com/thealgorithms/divideandconquer/CountingInversionsTest.java
+++ b/src/test/java/com/thealgorithms/divideandconquer/CountingInversionsTest.java
@@ -29,4 +29,35 @@ public void testAllInversions() {
int[] arr = {5, 4, 3, 2, 1};
assertEquals(10, CountingInversions.countInversions(arr));
}
+
+ @Test
+ public void testEmptyArray() {
+ int[] arr = {};
+ assertEquals(0, CountingInversions.countInversions(arr));
+ }
+
+ @Test
+ public void testArrayWithDuplicates() {
+ int[] arr = {1, 3, 2, 3, 1};
+ // Inversions: (3,2), (3,1), (3,1), (2,1)
+ assertEquals(4, CountingInversions.countInversions(arr));
+ }
+
+ @Test
+ public void testLargeArray() {
+ int n = 1000;
+ int[] arr = new int[n];
+ for (int i = 0; i < n; i++) {
+ arr[i] = n - i; // descending order -> max inversions = n*(n-1)/2
+ }
+ int expected = n * (n - 1) / 2;
+ assertEquals(expected, CountingInversions.countInversions(arr));
+ }
+
+ @Test
+ public void testArrayWithAllSameElements() {
+ int[] arr = {7, 7, 7, 7};
+ // No inversions since all elements are equal
+ assertEquals(0, CountingInversions.countInversions(arr));
+ }
}
From d6a871e683e33a6045c3d11181b3be7c8772d0dc Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Fri, 18 Jul 2025 23:55:37 +0300
Subject: [PATCH 17/57] testing: improve test coverage `RangeInSortedArrayTest`
(#6395)
* testing: improve test coverage RangeInSortedArrayTest
* style: fix formatting checkstyle
---------
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../misc/RangeInSortedArrayTest.java | 22 +++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java b/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java
index 7630d3e78dc7..543c66130449 100644
--- a/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java
+++ b/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java
@@ -32,4 +32,26 @@ private static Stream provideGetCountLessThanTestCases() {
return Stream.of(Arguments.of(new int[] {1, 2, 3, 3, 4, 5}, 3, 4, "Count of elements less than existing key"), Arguments.of(new int[] {1, 2, 3, 3, 4, 5}, 4, 5, "Count of elements less than non-existing key"), Arguments.of(new int[] {1, 2, 2, 3}, 5, 4, "Count with all smaller elements"),
Arguments.of(new int[] {2, 3, 4, 5}, 1, 0, "Count with no smaller elements"), Arguments.of(new int[] {}, 1, 0, "Count in empty array"));
}
+
+ @ParameterizedTest(name = "Edge case {index}: {3}")
+ @MethodSource("provideEdgeCasesForSortedRange")
+ void testSortedRangeEdgeCases(int[] nums, int key, int[] expectedRange, String description) {
+ assertArrayEquals(expectedRange, RangeInSortedArray.sortedRange(nums, key), description);
+ }
+
+ private static Stream provideEdgeCasesForSortedRange() {
+ return Stream.of(Arguments.of(new int[] {5, 5, 5, 5, 5}, 5, new int[] {0, 4}, "All elements same as key"), Arguments.of(new int[] {1, 2, 3, 4, 5}, 1, new int[] {0, 0}, "Key is first element"), Arguments.of(new int[] {1, 2, 3, 4, 5}, 5, new int[] {4, 4}, "Key is last element"),
+ Arguments.of(new int[] {1, 2, 3, 4, 5}, 0, new int[] {-1, -1}, "Key less than all elements"), Arguments.of(new int[] {1, 2, 3, 4, 5}, 6, new int[] {-1, -1}, "Key greater than all elements"),
+ Arguments.of(new int[] {1, 2, 2, 2, 3, 3, 3, 4}, 3, new int[] {4, 6}, "Multiple occurrences spread"), Arguments.of(new int[] {2}, 2, new int[] {0, 0}, "Single element array key exists"), Arguments.of(new int[] {2}, 3, new int[] {-1, -1}, "Single element array key missing"));
+ }
+
+ @ParameterizedTest(name = "Edge case {index}: {3}")
+ @MethodSource("provideEdgeCasesForGetCountLessThan")
+ void testGetCountLessThanEdgeCases(int[] nums, int key, int expectedCount, String description) {
+ assertEquals(expectedCount, RangeInSortedArray.getCountLessThan(nums, key), description);
+ }
+
+ private static Stream provideEdgeCasesForGetCountLessThan() {
+ return Stream.of(Arguments.of(new int[] {1, 2, 3, 4, 5}, 0, 0, "Key less than all elements"), Arguments.of(new int[] {1, 2, 3, 4, 5}, 6, 5, "Key greater than all elements"), Arguments.of(new int[] {1}, 0, 0, "Single element greater than key"));
+ }
}
From 5c6d3c3443d33d968724e5cb0e58588b8ebbc4a3 Mon Sep 17 00:00:00 2001
From: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
Date: Fri, 18 Jul 2025 23:01:50 +0200
Subject: [PATCH 18/57] Update DIRECTORY.md (#6414)
Co-authored-by: DenizAltunkapan
---
DIRECTORY.md | 12 +++---------
1 file changed, 3 insertions(+), 9 deletions(-)
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 9697dbc09869..b399044a0481 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -1,5 +1,7 @@
# Project Structure
+## src
+
- 📁 **main**
- 📁 **java**
- 📁 **com**
@@ -165,12 +167,10 @@
- 📄 [Kruskal](src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java)
- 📄 [MatrixGraphs](src/main/java/com/thealgorithms/datastructures/graphs/MatrixGraphs.java)
- 📄 [PrimMST](src/main/java/com/thealgorithms/datastructures/graphs/PrimMST.java)
- - 📄 [README](src/main/java/com/thealgorithms/datastructures/graphs/README.md)
- 📄 [TarjansAlgorithm](src/main/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithm.java)
- 📄 [UndirectedAdjacencyListGraph](src/main/java/com/thealgorithms/datastructures/graphs/UndirectedAdjacencyListGraph.java)
- 📄 [WelshPowell](src/main/java/com/thealgorithms/datastructures/graphs/WelshPowell.java)
- 📁 **hashmap**
- - 📄 [Readme](src/main/java/com/thealgorithms/datastructures/hashmap/Readme.md)
- 📁 **hashing**
- 📄 [GenericHashMapUsingArray](src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java)
- 📄 [GenericHashMapUsingArrayList](src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayList.java)
@@ -194,7 +194,6 @@
- 📄 [MergeKSortedArrays](src/main/java/com/thealgorithms/datastructures/heaps/MergeKSortedArrays.java)
- 📄 [MinHeap](src/main/java/com/thealgorithms/datastructures/heaps/MinHeap.java)
- 📄 [MinPriorityQueue](src/main/java/com/thealgorithms/datastructures/heaps/MinPriorityQueue.java)
- - 📄 [Readme](src/main/java/com/thealgorithms/datastructures/heaps/Readme.md)
- 📁 **lists**
- 📄 [CircleLinkedList](src/main/java/com/thealgorithms/datastructures/lists/CircleLinkedList.java)
- 📄 [CountSinglyLinkedListRecursion](src/main/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursion.java)
@@ -205,7 +204,6 @@
- 📄 [MergeSortedArrayList](src/main/java/com/thealgorithms/datastructures/lists/MergeSortedArrayList.java)
- 📄 [MergeSortedSinglyLinkedList](src/main/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedList.java)
- 📄 [QuickSortLinkedList](src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java)
- - 📄 [README](src/main/java/com/thealgorithms/datastructures/lists/README.md)
- 📄 [RandomNode](src/main/java/com/thealgorithms/datastructures/lists/RandomNode.java)
- 📄 [ReverseKGroup](src/main/java/com/thealgorithms/datastructures/lists/ReverseKGroup.java)
- 📄 [RotateSinglyLinkedLists](src/main/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedLists.java)
@@ -222,12 +220,10 @@
- 📄 [PriorityQueues](src/main/java/com/thealgorithms/datastructures/queues/PriorityQueues.java)
- 📄 [Queue](src/main/java/com/thealgorithms/datastructures/queues/Queue.java)
- 📄 [QueueByTwoStacks](src/main/java/com/thealgorithms/datastructures/queues/QueueByTwoStacks.java)
- - 📄 [README](src/main/java/com/thealgorithms/datastructures/queues/README.md)
- 📄 [SlidingWindowMaximum](src/main/java/com/thealgorithms/datastructures/queues/SlidingWindowMaximum.java)
- 📄 [TokenBucket](src/main/java/com/thealgorithms/datastructures/queues/TokenBucket.java)
- 📁 **stacks**
- 📄 [NodeStack](src/main/java/com/thealgorithms/datastructures/stacks/NodeStack.java)
- - 📄 [README](src/main/java/com/thealgorithms/datastructures/stacks/README.md)
- 📄 [ReverseStack](src/main/java/com/thealgorithms/datastructures/stacks/ReverseStack.java)
- 📄 [Stack](src/main/java/com/thealgorithms/datastructures/stacks/Stack.java)
- 📄 [StackArray](src/main/java/com/thealgorithms/datastructures/stacks/StackArray.java)
@@ -259,7 +255,6 @@
- 📄 [PreOrderTraversal](src/main/java/com/thealgorithms/datastructures/trees/PreOrderTraversal.java)
- 📄 [PrintTopViewofTree](src/main/java/com/thealgorithms/datastructures/trees/PrintTopViewofTree.java)
- 📄 [QuadTree](src/main/java/com/thealgorithms/datastructures/trees/QuadTree.java)
- - 📄 [README](src/main/java/com/thealgorithms/datastructures/trees/README.md)
- 📄 [RedBlackBST](src/main/java/com/thealgorithms/datastructures/trees/RedBlackBST.java)
- 📄 [SameTreesCheck](src/main/java/com/thealgorithms/datastructures/trees/SameTreesCheck.java)
- 📄 [SegmentTree](src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java)
@@ -749,7 +744,6 @@
- 📄 [ValidParentheses](src/main/java/com/thealgorithms/strings/ValidParentheses.java)
- 📄 [WordLadder](src/main/java/com/thealgorithms/strings/WordLadder.java)
- 📁 **zigZagPattern**
- - 📄 [README](src/main/java/com/thealgorithms/strings/zigZagPattern/README.md)
- 📄 [ZigZagPattern](src/main/java/com/thealgorithms/strings/zigZagPattern/ZigZagPattern.java)
- 📁 **tree**
- 📄 [HeavyLightDecomposition](src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java)
@@ -1416,4 +1410,4 @@
- 📁 **zigZagPattern**
- 📄 [ZigZagPatternTest](src/test/java/com/thealgorithms/strings/zigZagPattern/ZigZagPatternTest.java)
- 📁 **tree**
- - 📄 [HeavyLightDecompositionTest](src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java)
\ No newline at end of file
+ - 📄 [HeavyLightDecompositionTest](src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java)
From 76aea4254c732fa631acb0a9a47fda38389805b3 Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Sat, 19 Jul 2025 10:57:58 +0300
Subject: [PATCH 19/57] testing: improving test coverage `DisjointSetUnionTest`
(#6394)
* testing: improving test coverage DisjointSetUnionTest
* style: remove redundant comment
* testing: removing unused variable
---------
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../DisjointSetUnionTest.java | 102 +++++++++++++++---
1 file changed, 88 insertions(+), 14 deletions(-)
diff --git a/src/test/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionTest.java b/src/test/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionTest.java
index a10a99d40496..14dceb53c40d 100644
--- a/src/test/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionTest.java
@@ -1,8 +1,9 @@
package com.thealgorithms.datastructures.disjointsetunion;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class DisjointSetUnionTest {
@@ -12,7 +13,7 @@ public void testMakeSet() {
DisjointSetUnion dsu = new DisjointSetUnion<>();
Node node = dsu.makeSet(1);
assertNotNull(node);
- Assertions.assertEquals(node, node.parent);
+ assertEquals(node, node.parent);
}
@Test
@@ -33,19 +34,92 @@ public void testUnionFindSet() {
Node root3 = dsu.findSet(node3);
Node root4 = dsu.findSet(node4);
- Assertions.assertEquals(node1, node1.parent);
- Assertions.assertEquals(node1, node2.parent);
- Assertions.assertEquals(node1, node3.parent);
- Assertions.assertEquals(node1, node4.parent);
+ assertEquals(node1, node1.parent);
+ assertEquals(node1, node2.parent);
+ assertEquals(node1, node3.parent);
+ assertEquals(node1, node4.parent);
- Assertions.assertEquals(node1, root1);
- Assertions.assertEquals(node1, root2);
- Assertions.assertEquals(node1, root3);
- Assertions.assertEquals(node1, root4);
+ assertEquals(node1, root1);
+ assertEquals(node1, root2);
+ assertEquals(node1, root3);
+ assertEquals(node1, root4);
- Assertions.assertEquals(1, node1.rank);
- Assertions.assertEquals(0, node2.rank);
- Assertions.assertEquals(0, node3.rank);
- Assertions.assertEquals(0, node4.rank);
+ assertEquals(1, node1.rank);
+ assertEquals(0, node2.rank);
+ assertEquals(0, node3.rank);
+ assertEquals(0, node4.rank);
+ }
+
+ @Test
+ public void testFindSetOnSingleNode() {
+ DisjointSetUnion dsu = new DisjointSetUnion<>();
+ Node node = dsu.makeSet("A");
+ assertEquals(node, dsu.findSet(node));
+ }
+
+ @Test
+ public void testUnionAlreadyConnectedNodes() {
+ DisjointSetUnion dsu = new DisjointSetUnion<>();
+ Node node1 = dsu.makeSet(1);
+ Node node2 = dsu.makeSet(2);
+ Node node3 = dsu.makeSet(3);
+
+ dsu.unionSets(node1, node2);
+ dsu.unionSets(node2, node3);
+
+ // Union nodes that are already connected
+ dsu.unionSets(node1, node3);
+
+ // All should have the same root
+ Node root = dsu.findSet(node1);
+ assertEquals(root, dsu.findSet(node2));
+ assertEquals(root, dsu.findSet(node3));
+ }
+
+ @Test
+ public void testRankIncrease() {
+ DisjointSetUnion dsu = new DisjointSetUnion<>();
+ Node node1 = dsu.makeSet(1);
+ Node node2 = dsu.makeSet(2);
+ Node node3 = dsu.makeSet(3);
+ Node node4 = dsu.makeSet(4);
+
+ dsu.unionSets(node1, node2); // rank of node1 should increase
+ dsu.unionSets(node3, node4); // rank of node3 should increase
+ dsu.unionSets(node1, node3); // union two trees of same rank -> rank increases
+
+ assertEquals(2, dsu.findSet(node1).rank);
+ }
+
+ @Test
+ public void testMultipleMakeSets() {
+ DisjointSetUnion dsu = new DisjointSetUnion<>();
+ Node node1 = dsu.makeSet(1);
+ Node node2 = dsu.makeSet(2);
+ Node node3 = dsu.makeSet(3);
+
+ assertNotEquals(node1, node2);
+ assertNotEquals(node2, node3);
+ assertNotEquals(node1, node3);
+
+ assertEquals(node1, node1.parent);
+ assertEquals(node2, node2.parent);
+ assertEquals(node3, node3.parent);
+ }
+
+ @Test
+ public void testPathCompression() {
+ DisjointSetUnion dsu = new DisjointSetUnion<>();
+ Node node1 = dsu.makeSet(1);
+ Node node2 = dsu.makeSet(2);
+ Node node3 = dsu.makeSet(3);
+
+ dsu.unionSets(node1, node2);
+ dsu.unionSets(node2, node3);
+
+ // After findSet, path compression should update parent to root directly
+ Node root = dsu.findSet(node3);
+ assertEquals(root, node1);
+ assertEquals(node1, node3.parent);
}
}
From d14e8a60e8b0c7e133d48c1161df7604e2aa0894 Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Sat, 19 Jul 2025 11:00:49 +0300
Subject: [PATCH 20/57] testing: improve test coverage `DuplicateBracketsTest`
(#6396)
* testing: improve test coverage DuplicateBracketsTest
* style: fix formatting checkstyle
* style: fix formatting checkstyle
---------
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../stacks/DuplicateBracketsTest.java | 30 +++++++++++++++++--
1 file changed, 28 insertions(+), 2 deletions(-)
diff --git a/src/test/java/com/thealgorithms/stacks/DuplicateBracketsTest.java b/src/test/java/com/thealgorithms/stacks/DuplicateBracketsTest.java
index e2cc6acb8112..bc7b3266d98e 100644
--- a/src/test/java/com/thealgorithms/stacks/DuplicateBracketsTest.java
+++ b/src/test/java/com/thealgorithms/stacks/DuplicateBracketsTest.java
@@ -4,20 +4,23 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.MethodSource;
class DuplicateBracketsTest {
@ParameterizedTest
- @CsvSource({"'((a + b) + (c + d))'", "'(a + b)'", "'a + b'", "'('", "''"})
+ @CsvSource({"'((a + b) + (c + d))'", "'(a + b)'", "'a + b'", "'('", "''", "'a + (b * c) - d'", "'(x + y) * (z)'", "'(a + (b - c))'"})
void testInputReturnsFalse(String input) {
assertFalse(DuplicateBrackets.check(input));
}
@ParameterizedTest
- @CsvSource({"'(a + b) + ((c + d))'", "'((a + b))'", "'((((a + b)))))'"})
+ @CsvSource({"'(a + b) + ((c + d))'", "'((a + b))'", "'((((a + b)))))'", "'((x))'", "'((a + (b)))'", "'(a + ((b)))'", "'(((a)))'", "'(((())))'"})
void testInputReturnsTrue(String input) {
assertTrue(DuplicateBrackets.check(input));
}
@@ -26,4 +29,27 @@ void testInputReturnsTrue(String input) {
void testInvalidInput() {
assertThrows(IllegalArgumentException.class, () -> DuplicateBrackets.check(null));
}
+
+ @ParameterizedTest(name = "Should be true: \"{0}\"")
+ @MethodSource("provideInputsThatShouldReturnTrue")
+ void testDuplicateBracketsTrueCases(String input) {
+ assertTrue(DuplicateBrackets.check(input));
+ }
+
+ static Stream provideInputsThatShouldReturnTrue() {
+ return Stream.of(Arguments.of("()"), Arguments.of("(( ))"));
+ }
+
+ @ParameterizedTest(name = "Should be false: \"{0}\"")
+ @MethodSource("provideInputsThatShouldReturnFalse")
+ void testDuplicateBracketsFalseCases(String input) {
+ assertFalse(DuplicateBrackets.check(input));
+ }
+
+ static Stream provideInputsThatShouldReturnFalse() {
+ return Stream.of(Arguments.of("( )"), // whitespace inside brackets
+ Arguments.of("abc + def"), // no brackets
+ Arguments.of("(a + (b * c)) - (d / e)") // complex, but no duplicates
+ );
+ }
}
From 334543f54c0e64a4fcdbfc162830842c9118c1cf Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Sat, 19 Jul 2025 17:04:10 +0300
Subject: [PATCH 21/57] testing: improve test coverage `PriorityQueuesTest`
(#6397)
testing: improve test coverage PriorityQueuesTest
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../queues/PriorityQueuesTest.java | 56 +++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/src/test/java/com/thealgorithms/datastructures/queues/PriorityQueuesTest.java b/src/test/java/com/thealgorithms/datastructures/queues/PriorityQueuesTest.java
index 1a3b5aadebb2..e97fe091c556 100644
--- a/src/test/java/com/thealgorithms/datastructures/queues/PriorityQueuesTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/queues/PriorityQueuesTest.java
@@ -55,4 +55,60 @@ void testPQExtra() {
Assertions.assertEquals(myQueue.peek(), 2);
Assertions.assertEquals(myQueue.getSize(), 1);
}
+
+ @Test
+ void testInsertUntilFull() {
+ PriorityQueue pq = new PriorityQueue(3);
+ pq.insert(1);
+ pq.insert(4);
+ pq.insert(2);
+ Assertions.assertTrue(pq.isFull());
+ Assertions.assertEquals(4, pq.peek());
+ }
+
+ @Test
+ void testRemoveFromEmpty() {
+ PriorityQueue pq = new PriorityQueue(3);
+ Assertions.assertThrows(RuntimeException.class, pq::remove);
+ }
+
+ @Test
+ void testInsertDuplicateValues() {
+ PriorityQueue pq = new PriorityQueue(5);
+ pq.insert(5);
+ pq.insert(5);
+ pq.insert(3);
+ Assertions.assertEquals(5, pq.peek());
+ pq.remove();
+ Assertions.assertEquals(5, pq.peek());
+ pq.remove();
+ Assertions.assertEquals(3, pq.peek());
+ }
+
+ @Test
+ void testSizeAfterInsertAndRemove() {
+ PriorityQueue pq = new PriorityQueue(4);
+ Assertions.assertEquals(0, pq.getSize());
+ pq.insert(2);
+ Assertions.assertEquals(1, pq.getSize());
+ pq.insert(10);
+ Assertions.assertEquals(2, pq.getSize());
+ pq.remove();
+ Assertions.assertEquals(1, pq.getSize());
+ pq.remove();
+ Assertions.assertEquals(0, pq.getSize());
+ }
+
+ @Test
+ void testInsertAndRemoveAll() {
+ PriorityQueue pq = new PriorityQueue(3);
+ pq.insert(8);
+ pq.insert(1);
+ pq.insert(6);
+ Assertions.assertTrue(pq.isFull());
+ pq.remove();
+ pq.remove();
+ pq.remove();
+ Assertions.assertTrue(pq.isEmpty());
+ }
}
From 0a46b828c2daf173d8dec87561a900568780a683 Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Sat, 19 Jul 2025 17:07:54 +0300
Subject: [PATCH 22/57] testing: Enhance `ValidParenthesesTest` (#6398)
* testing: improve test coverage ValidParenthesesTest
* style: fix formatting for checkstyle
* style: fix formatting for checkstyle
* style: fix import
---------
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../strings/ValidParenthesesTest.java | 22 +++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java b/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java
index 23d41b159fe2..411b11e743b8 100644
--- a/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java
+++ b/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java
@@ -1,15 +1,33 @@
package com.thealgorithms.strings;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
public class ValidParenthesesTest {
@ParameterizedTest(name = "Input: \"{0}\" → Expected: {1}")
- @CsvSource({"'()', true", "'()[]{}', true", "'(]', false", "'{[]}', true", "'([{}])', true", "'([)]', false", "'', true", "'(', false", "')', false"})
- void testIsValid(String input, boolean expected) {
+ @CsvSource({"'()', true", "'()[]{}', true", "'(]', false", "'{[]}', true", "'([{}])', true", "'([)]', false", "'', true", "'(', false", "')', false", "'{{{{}}}}', true", "'[({})]', true", "'[(])', false", "'[', false", "']', false", "'()()()()', true", "'(()', false", "'())', false",
+ "'{[()()]()}', true"})
+ void
+ testIsValid(String input, boolean expected) {
assertEquals(expected, ValidParentheses.isValid(input));
}
+
+ @Test
+ void testNullInputThrows() {
+ IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> ValidParentheses.isValid(null));
+ assertEquals("Input string cannot be null", ex.getMessage());
+ }
+
+ @ParameterizedTest(name = "Input: \"{0}\" → throws IllegalArgumentException")
+ @CsvSource({"'a'", "'()a'", "'[123]'", "'{hello}'", "'( )'", "'\t'", "'\n'", "'@#$%'"})
+ void testInvalidCharactersThrow(String input) {
+ IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> ValidParentheses.isValid(input));
+ assertTrue(ex.getMessage().startsWith("Unexpected character"));
+ }
}
From c7af421dfa1fd5d0c3aa54890d2f4ece0df59324 Mon Sep 17 00:00:00 2001
From: justakayy
Date: Sun, 20 Jul 2025 09:59:18 +0200
Subject: [PATCH 23/57] test: PointTest.java #HSFDPMUW (#6391)
* test: added Tests for Point.java
* style: fixed formatting and comments
* style: formatted with clang-format and renamed variables
* style: fixed imports to not use the '.*' form
---------
Co-authored-by: Aaron
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../com/thealgorithms/geometry/PointTest.java | 117 ++++++++++++++++++
1 file changed, 117 insertions(+)
create mode 100644 src/test/java/com/thealgorithms/geometry/PointTest.java
diff --git a/src/test/java/com/thealgorithms/geometry/PointTest.java b/src/test/java/com/thealgorithms/geometry/PointTest.java
new file mode 100644
index 000000000000..12901364b458
--- /dev/null
+++ b/src/test/java/com/thealgorithms/geometry/PointTest.java
@@ -0,0 +1,117 @@
+package com.thealgorithms.geometry;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class PointTest {
+
+ @Test
+ void testCompareTo() {
+ Point p1 = new Point(1, 2);
+ Point p2 = new Point(5, -1);
+ Point p3 = new Point(3, 9);
+ Point p4 = new Point(3, 9);
+ assertEquals(1, p1.compareTo(p2));
+ assertEquals(-1, p2.compareTo(p3));
+ assertEquals(0, p3.compareTo(p4));
+ }
+
+ @Test
+ void testToString() {
+ Point p = new Point(-3, 5);
+ assertEquals("(-3, 5)", p.toString());
+ }
+
+ @Test
+ void testPolarOrder() {
+ Point p = new Point(0, 0);
+ assertNotNull(p.polarOrder());
+ }
+
+ @Test
+ void testOrientation() {
+ // setup points
+ Point pA = new Point(0, 0);
+ Point pB = new Point(1, 0);
+ Point pC = new Point(1, 1);
+
+ // test for left curve
+ assertEquals(1, Point.orientation(pA, pB, pC));
+
+ // test for right curve
+ pB = new Point(0, 1);
+ assertEquals(-1, Point.orientation(pA, pB, pC));
+
+ // test for left curve
+ pC = new Point(-1, 1);
+ assertEquals(1, Point.orientation(pA, pB, pC));
+
+ // test for right curve
+ pB = new Point(1, 0);
+ pC = new Point(1, -1);
+ assertEquals(-1, Point.orientation(pA, pB, pC));
+
+ // test for collinearity
+ pB = new Point(1, 1);
+ pC = new Point(2, 2);
+ assertEquals(0, Point.orientation(pA, pB, pC));
+ }
+
+ @Test
+ void testPolarOrderCompare() {
+ Point ref = new Point(0, 0);
+
+ Point pA = new Point(1, 1);
+ Point pB = new Point(1, -1);
+ assertTrue(ref.polarOrder().compare(pA, pB) < 0);
+
+ pA = new Point(3, 0);
+ pB = new Point(2, 0);
+ assertTrue(ref.polarOrder().compare(pA, pB) < 0);
+
+ pA = new Point(0, 1);
+ pB = new Point(-1, 1);
+ assertTrue(ref.polarOrder().compare(pA, pB) < 0);
+
+ pA = new Point(1, 1);
+ pB = new Point(2, 2);
+ assertEquals(0, ref.polarOrder().compare(pA, pB));
+
+ pA = new Point(1, 2);
+ pB = new Point(2, 1);
+ assertTrue(ref.polarOrder().compare(pA, pB) > 0);
+
+ pA = new Point(2, 1);
+ pB = new Point(1, 2);
+ assertTrue(ref.polarOrder().compare(pA, pB) < 0);
+
+ pA = new Point(-1, 0);
+ pB = new Point(-2, 0);
+ assertTrue(ref.polarOrder().compare(pA, pB) < 0);
+
+ pA = new Point(2, 3);
+ pB = new Point(2, 3);
+ assertEquals(0, ref.polarOrder().compare(pA, pB));
+
+ pA = new Point(0, 1);
+ pB = new Point(0, -1);
+ assertTrue(ref.polarOrder().compare(pA, pB) < 0);
+
+ ref = new Point(1, 1);
+
+ pA = new Point(1, 2);
+ pB = new Point(2, 2);
+ assertTrue(ref.polarOrder().compare(pA, pB) > 0);
+
+ pA = new Point(2, 1);
+ pB = new Point(2, 0);
+ assertTrue(ref.polarOrder().compare(pA, pB) < 0);
+
+ pA = new Point(0, 1);
+ pB = new Point(1, 0);
+ assertTrue(ref.polarOrder().compare(pA, pB) < 0);
+ }
+}
From 171fdc9925f3656a58d00ff2e3e4b60c547a0778 Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Sun, 20 Jul 2025 11:03:23 +0300
Subject: [PATCH 24/57] testing: improving `CRCAlgorithmTest` (#6403)
* testing: improving CRCAlgorithmTest
* style: fix formatting
---------
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../others/CRCAlgorithmTest.java | 71 ++++++++++++++++---
1 file changed, 62 insertions(+), 9 deletions(-)
diff --git a/src/test/java/com/thealgorithms/others/CRCAlgorithmTest.java b/src/test/java/com/thealgorithms/others/CRCAlgorithmTest.java
index a581a35bf963..542c256a3e88 100644
--- a/src/test/java/com/thealgorithms/others/CRCAlgorithmTest.java
+++ b/src/test/java/com/thealgorithms/others/CRCAlgorithmTest.java
@@ -1,29 +1,82 @@
-
package com.thealgorithms.others;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
public class CRCAlgorithmTest {
@Test
- void test1() {
+ void testNoErrorsWithZeroBER() {
CRCAlgorithm c = new CRCAlgorithm("10010101010100101010010000001010010101010", 10, 0.0);
-
- // A bit-error rate of 0.0 should not provide any wrong messages
- c.changeMess();
+ c.generateRandomMess();
c.divideMessageWithP(false);
- assertEquals(c.getWrongMess(), 0);
+ c.changeMess();
+ c.divideMessageWithP(true);
+ assertEquals(0, c.getWrongMess(), "BER=0 should produce no wrong messages");
+ assertEquals(0, c.getWrongMessCaught(), "No errors, so no caught wrong messages");
+ assertEquals(0, c.getWrongMessNotCaught(), "No errors, so no uncaught wrong messages");
+ assertTrue(c.getCorrectMess() > 0, "Should have some correct messages");
}
@Test
- void test2() {
+ void testAllErrorsWithBEROne() {
CRCAlgorithm c = new CRCAlgorithm("10010101010100101010010000001010010101010", 10, 1.0);
+ c.generateRandomMess();
+ c.divideMessageWithP(false);
+ c.changeMess();
+ c.divideMessageWithP(true);
+ assertTrue(c.getWrongMess() > 0, "BER=1 should produce wrong messages");
+ assertEquals(0, c.getCorrectMess(), "BER=1 should produce no correct messages");
+ }
- // A bit error rate of 1.0 should not provide any correct messages
+ @Test
+ void testIntermediateBER() {
+ CRCAlgorithm c = new CRCAlgorithm("1101", 4, 0.5);
+ c.generateRandomMess();
+ for (int i = 0; i < 1000; i++) {
+ c.refactor();
+ c.generateRandomMess();
+ c.divideMessageWithP(false);
+ c.changeMess();
+ c.divideMessageWithP(true);
+ }
+ assertTrue(c.getWrongMess() > 0, "Some wrong messages expected with BER=0.5");
+ assertTrue(c.getWrongMessCaught() >= 0, "Wrong messages caught counter >= 0");
+ assertTrue(c.getWrongMessNotCaught() >= 0, "Wrong messages not caught counter >= 0");
+ assertTrue(c.getCorrectMess() >= 0, "Correct messages counter >= 0");
+ assertEquals(c.getWrongMess(), c.getWrongMessCaught() + c.getWrongMessNotCaught(), "Sum of caught and not caught wrong messages should equal total wrong messages");
+ }
+
+ @Test
+ void testMessageChangedFlag() {
+ CRCAlgorithm c = new CRCAlgorithm("1010", 4, 1.0);
+ c.generateRandomMess();
+ c.divideMessageWithP(false);
+ c.changeMess();
+ assertTrue(c.getWrongMess() > 0, "Message should be marked as changed with BER=1");
+ }
+
+ @Test
+ void testSmallMessageSize() {
+ CRCAlgorithm c = new CRCAlgorithm("11", 2, 0.0);
+ c.generateRandomMess();
+ c.divideMessageWithP(false);
c.changeMess();
+ c.divideMessageWithP(true);
+ assertEquals(0, c.getWrongMess(), "No errors expected for BER=0 with small message");
+ }
+
+ @Test
+ void testLargeMessageSize() {
+ CRCAlgorithm c = new CRCAlgorithm("1101", 1000, 0.01);
+ c.generateRandomMess();
c.divideMessageWithP(false);
- assertEquals(c.getCorrectMess(), 0);
+ c.changeMess();
+ c.divideMessageWithP(true);
+ // Just ensure counters are updated, no exceptions
+ assertTrue(c.getWrongMess() >= 0);
+ assertTrue(c.getCorrectMess() >= 0);
}
}
From b45fd2a6560b9e7f2732414547c98caf3c16582b Mon Sep 17 00:00:00 2001
From: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
Date: Sun, 20 Jul 2025 10:13:12 +0200
Subject: [PATCH 25/57] Update DIRECTORY.md (#6419)
Co-authored-by: DenizAltunkapan
---
DIRECTORY.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/DIRECTORY.md b/DIRECTORY.md
index b399044a0481..cae5b8067c41 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -1042,6 +1042,7 @@
- 📄 [GrahamScanTest](src/test/java/com/thealgorithms/geometry/GrahamScanTest.java)
- 📄 [MidpointCircleTest](src/test/java/com/thealgorithms/geometry/MidpointCircleTest.java)
- 📄 [MidpointEllipseTest](src/test/java/com/thealgorithms/geometry/MidpointEllipseTest.java)
+ - 📄 [PointTest](src/test/java/com/thealgorithms/geometry/PointTest.java)
- 📁 **graph**
- 📄 [ConstrainedShortestPathTest](src/test/java/com/thealgorithms/graph/ConstrainedShortestPathTest.java)
- 📄 [StronglyConnectedComponentOptimizedTest](src/test/java/com/thealgorithms/graph/StronglyConnectedComponentOptimizedTest.java)
From 31bf130e9eb14409c6f628f0a64a419fc6564e61 Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Sun, 20 Jul 2025 11:21:46 +0300
Subject: [PATCH 26/57] refactor: improving `Median` (#6404)
refactor: improving Median
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
src/main/java/com/thealgorithms/maths/Median.java | 5 +++++
src/test/java/com/thealgorithms/maths/MedianTest.java | 7 +++++++
2 files changed, 12 insertions(+)
diff --git a/src/main/java/com/thealgorithms/maths/Median.java b/src/main/java/com/thealgorithms/maths/Median.java
index e4daec8fc11a..fd2aab84e4e9 100644
--- a/src/main/java/com/thealgorithms/maths/Median.java
+++ b/src/main/java/com/thealgorithms/maths/Median.java
@@ -13,8 +13,13 @@ private Median() {
* Calculate average median
* @param values sorted numbers to find median of
* @return median of given {@code values}
+ * @throws IllegalArgumentException If the input array is empty or null.
*/
public static double median(int[] values) {
+ if (values == null || values.length == 0) {
+ throw new IllegalArgumentException("Values array cannot be empty or null");
+ }
+
Arrays.sort(values);
int length = values.length;
return length % 2 == 0 ? (values[length / 2] + values[length / 2 - 1]) / 2.0 : values[length / 2];
diff --git a/src/test/java/com/thealgorithms/maths/MedianTest.java b/src/test/java/com/thealgorithms/maths/MedianTest.java
index d2b637abd3cb..560feb695792 100644
--- a/src/test/java/com/thealgorithms/maths/MedianTest.java
+++ b/src/test/java/com/thealgorithms/maths/MedianTest.java
@@ -1,6 +1,7 @@
package com.thealgorithms.maths;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
@@ -34,4 +35,10 @@ void medianNegativeValues() {
int[] arr = {-27, -16, -7, -4, -2, -1};
assertEquals(-5.5, Median.median(arr));
}
+
+ @Test
+ void medianEmptyArrayThrows() {
+ int[] arr = {};
+ assertThrows(IllegalArgumentException.class, () -> Median.median(arr));
+ }
}
From 0e9be57ed44e3772dcc5be097b5faeb71a70f1f7 Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Mon, 21 Jul 2025 17:55:07 +0200
Subject: [PATCH 27/57] testing: improving `PostfixEvaluatorTest` (#6405)
* testing: improving PostfixEvaluatorTest
* testing: redundant cases
---
.../stacks/PostfixEvaluatorTest.java | 31 ++++++++++++++-----
1 file changed, 24 insertions(+), 7 deletions(-)
diff --git a/src/test/java/com/thealgorithms/stacks/PostfixEvaluatorTest.java b/src/test/java/com/thealgorithms/stacks/PostfixEvaluatorTest.java
index 882fe644ccd5..682240acd752 100644
--- a/src/test/java/com/thealgorithms/stacks/PostfixEvaluatorTest.java
+++ b/src/test/java/com/thealgorithms/stacks/PostfixEvaluatorTest.java
@@ -4,24 +4,41 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.EmptyStackException;
+import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
public class PostfixEvaluatorTest {
- @Test
- public void testValidExpressions() {
- assertEquals(22, PostfixEvaluator.evaluatePostfix("5 6 + 2 *"));
- assertEquals(27, PostfixEvaluator.evaluatePostfix("7 2 + 3 *"));
- assertEquals(3, PostfixEvaluator.evaluatePostfix("10 5 / 1 +"));
+ @ParameterizedTest(name = "Expression: \"{0}\" → Result: {1}")
+ @CsvSource({"'5 6 + 2 *', 22", "'7 2 + 3 *', 27", "'10 5 / 1 +', 3", "'8', 8", "'3 4 +', 7"})
+ @DisplayName("Valid postfix expressions")
+ void testValidExpressions(String expression, int expected) {
+ assertEquals(expected, PostfixEvaluator.evaluatePostfix(expression));
}
@Test
- public void testInvalidExpression() {
+ @DisplayName("Should throw EmptyStackException for incomplete expression")
+ void testInvalidExpression() {
assertThrows(EmptyStackException.class, () -> PostfixEvaluator.evaluatePostfix("5 +"));
}
@Test
- public void testMoreThanOneStackSizeAfterEvaluation() {
+ @DisplayName("Should throw IllegalArgumentException for extra operands")
+ void testExtraOperands() {
assertThrows(IllegalArgumentException.class, () -> PostfixEvaluator.evaluatePostfix("5 6 + 2 * 3"));
}
+
+ @Test
+ @DisplayName("Should throw ArithmeticException for division by zero")
+ void testDivisionByZero() {
+ assertThrows(ArithmeticException.class, () -> PostfixEvaluator.evaluatePostfix("1 0 /"));
+ }
+
+ @Test
+ @DisplayName("Should throw IllegalArgumentException for invalid characters")
+ void testInvalidToken() {
+ assertThrows(IllegalArgumentException.class, () -> PostfixEvaluator.evaluatePostfix("1 a +"));
+ }
}
From 75298bb3f4a4322cb15c3fd9bb981b40d8d158e9 Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Mon, 21 Jul 2025 17:58:22 +0200
Subject: [PATCH 28/57] testing: improving `DequeTest` (#6410)
* testing: improving DequeTest
* testing: redundant case
* testing: fix to many static imports
* testing: add more test cases
---------
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../datastructures/queues/DequeTest.java | 34 +++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/src/test/java/com/thealgorithms/datastructures/queues/DequeTest.java b/src/test/java/com/thealgorithms/datastructures/queues/DequeTest.java
index 1244a2e260d2..34b1ea2e0277 100644
--- a/src/test/java/com/thealgorithms/datastructures/queues/DequeTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/queues/DequeTest.java
@@ -87,4 +87,38 @@ void testToString() {
deque.addFirst(5);
assertEquals("Head -> 5 <-> 10 <-> 20 <- Tail", deque.toString());
}
+
+ @Test
+ void testAlternatingAddRemove() {
+ Deque deque = new Deque<>();
+ deque.addFirst(1);
+ deque.addLast(2);
+ deque.addFirst(0);
+ assertEquals(0, deque.pollFirst());
+ assertEquals(2, deque.pollLast());
+ assertEquals(1, deque.pollFirst());
+ org.junit.jupiter.api.Assertions.assertTrue(deque.isEmpty());
+ }
+
+ @Test
+ void testSizeAfterOperations() {
+ Deque deque = new Deque<>();
+ assertEquals(0, deque.size());
+ deque.addFirst(1);
+ deque.addLast(2);
+ deque.addFirst(3);
+ assertEquals(3, deque.size());
+ deque.pollFirst();
+ deque.pollLast();
+ assertEquals(1, deque.size());
+ }
+
+ @Test
+ void testNullValues() {
+ Deque deque = new Deque<>();
+ deque.addFirst(null);
+ assertNull(deque.peekFirst());
+ assertNull(deque.pollFirst());
+ org.junit.jupiter.api.Assertions.assertTrue(deque.isEmpty());
+ }
}
From 2722b0ecc990983614ef0e5ef5c4a39fd979b516 Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Mon, 21 Jul 2025 18:02:07 +0200
Subject: [PATCH 29/57] testing: improving `SkipListTest` (#6411)
* testing: improving SkipListTest
* style: fixed formatting
---------
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../datastructures/lists/SkipListTest.java | 119 +++++++++---------
1 file changed, 62 insertions(+), 57 deletions(-)
diff --git a/src/test/java/com/thealgorithms/datastructures/lists/SkipListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/SkipListTest.java
index c572739ffbbf..16d1a015a4d9 100644
--- a/src/test/java/com/thealgorithms/datastructures/lists/SkipListTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/lists/SkipListTest.java
@@ -1,110 +1,115 @@
package com.thealgorithms.datastructures.lists;
-import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Arrays;
import java.util.stream.IntStream;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
class SkipListTest {
+ private SkipList skipList;
+
+ @BeforeEach
+ void setUp() {
+ skipList = new SkipList<>();
+ }
+
@Test
- void add() {
- SkipList skipList = new SkipList<>();
+ @DisplayName("Add element and verify size and retrieval")
+ void testAdd() {
assertEquals(0, skipList.size());
skipList.add("value");
- print(skipList);
assertEquals(1, skipList.size());
+ assertEquals("value", skipList.get(0));
}
@Test
- void get() {
- SkipList skipList = new SkipList<>();
+ @DisplayName("Get retrieves correct element by index")
+ void testGet() {
skipList.add("value");
-
- String actualValue = skipList.get(0);
-
- print(skipList);
- assertEquals("value", actualValue);
+ assertEquals("value", skipList.get(0));
}
@Test
- void contains() {
- SkipList skipList = createSkipList();
- print(skipList);
-
- boolean contains = skipList.contains("b");
-
- assertTrue(contains);
+ @DisplayName("Contains returns true if element exists")
+ void testContains() {
+ skipList = createSkipList();
+ assertTrue(skipList.contains("b"));
+ assertTrue(skipList.contains("a"));
+ assertFalse(skipList.contains("z")); // negative test
}
@Test
- void removeFromHead() {
- SkipList skipList = createSkipList();
- String mostLeftElement = skipList.get(0);
+ @DisplayName("Remove element from head and check size and order")
+ void testRemoveFromHead() {
+ skipList = createSkipList();
+ String first = skipList.get(0);
int initialSize = skipList.size();
- print(skipList);
- skipList.remove(mostLeftElement);
+ skipList.remove(first);
- print(skipList);
assertEquals(initialSize - 1, skipList.size());
+ assertFalse(skipList.contains(first));
}
@Test
- void removeFromTail() {
- SkipList skipList = createSkipList();
- String mostRightValue = skipList.get(skipList.size() - 1);
+ @DisplayName("Remove element from tail and check size and order")
+ void testRemoveFromTail() {
+ skipList = createSkipList();
+ String last = skipList.get(skipList.size() - 1);
int initialSize = skipList.size();
- print(skipList);
- skipList.remove(mostRightValue);
+ skipList.remove(last);
- print(skipList);
assertEquals(initialSize - 1, skipList.size());
+ assertFalse(skipList.contains(last));
}
@Test
- void checkSortedOnLowestLayer() {
- SkipList skipList = new SkipList<>();
+ @DisplayName("Elements should be sorted at base level")
+ void testSortedOrderOnBaseLevel() {
String[] values = {"d", "b", "a", "c"};
Arrays.stream(values).forEach(skipList::add);
- print(skipList);
String[] actualOrder = IntStream.range(0, values.length).mapToObj(skipList::get).toArray(String[] ::new);
- assertArrayEquals(new String[] {"a", "b", "c", "d"}, actualOrder);
+ org.junit.jupiter.api.Assertions.assertArrayEquals(new String[] {"a", "b", "c", "d"}, actualOrder);
}
- private SkipList createSkipList() {
- SkipList skipList = new SkipList<>();
- String[] values = {
- "a",
- "b",
- "c",
- "d",
- "e",
- "f",
- "g",
- "h",
- "i",
- "j",
- "k",
- };
+ @Test
+ @DisplayName("Duplicate elements can be added and count correctly")
+ void testAddDuplicates() {
+ skipList.add("x");
+ skipList.add("x");
+ assertEquals(2, skipList.size());
+ assertEquals("x", skipList.get(0));
+ assertEquals("x", skipList.get(1));
+ }
+
+ @Test
+ @DisplayName("Add multiple and remove all")
+ void testClearViaRemovals() {
+ String[] values = {"a", "b", "c"};
Arrays.stream(values).forEach(skipList::add);
- return skipList;
+
+ for (String v : values) {
+ skipList.remove(v);
+ }
+
+ assertEquals(0, skipList.size());
}
- /**
- * Print Skip List representation to console.
- * Optional method not involved in testing process. Used only for visualisation purposes.
- * @param skipList to print
- */
- private void print(SkipList> skipList) {
- System.out.println(skipList);
+ private SkipList createSkipList() {
+ SkipList s = new SkipList<>();
+ String[] values = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"};
+ Arrays.stream(values).forEach(s::add);
+ return s;
}
}
From 2dfad7ef8feecfc0a2e40f729427e62fdabc1a0d Mon Sep 17 00:00:00 2001
From: Nishitha Wihala
Date: Mon, 21 Jul 2025 21:37:13 +0530
Subject: [PATCH 30/57] Add matrix multiplication with double[][] and unit
tests (#6417)
* MatrixMultiplication.java created and updated.
* Add necessary comment to MatrixMultiplication.java
* Create MatrixMultiplicationTest.java
* method for 2 by 2 matrix multiplication is created
* Use assertMatrixEquals(), otherwise there can be error due to floating point arithmetic errors
* assertMatrixEquals method created and updated
* method created for 3by2 matrix multiply with 2by1 matrix
* method created for null matrix multiplication
* method for test matrix dimension error
* method for test empty matrix input
* testMultiply3by2and2by1 test case updated
* Check for empty matrices part updated
* Updated Unit test coverage
* files updated
* clean the code
* clean the code
* Updated files with google-java-format
* Updated files
* Updated files
* Updated files
* Updated files
* Add reference links and complexities
* Add test cases for 1by1 matrix and non-rectangular matrix
* Add reference links and complexities
---------
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../matrix/MatrixMultiplication.java | 69 ++++++++++++
.../matrix/MatrixMultiplicationTest.java | 101 ++++++++++++++++++
2 files changed, 170 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/matrix/MatrixMultiplication.java
create mode 100644 src/test/java/com/thealgorithms/matrix/MatrixMultiplicationTest.java
diff --git a/src/main/java/com/thealgorithms/matrix/MatrixMultiplication.java b/src/main/java/com/thealgorithms/matrix/MatrixMultiplication.java
new file mode 100644
index 000000000000..6467a438577b
--- /dev/null
+++ b/src/main/java/com/thealgorithms/matrix/MatrixMultiplication.java
@@ -0,0 +1,69 @@
+package com.thealgorithms.matrix;
+
+/**
+ * This class provides a method to perform matrix multiplication.
+ *
+ *
Matrix multiplication takes two 2D arrays (matrices) as input and
+ * produces their product, following the mathematical definition of
+ * matrix multiplication.
+ *
+ *
For more details:
+ * https://www.geeksforgeeks.org/java/java-program-to-multiply-two-matrices-of-any-size/
+ * https://en.wikipedia.org/wiki/Matrix_multiplication
+ *
+ *
Time Complexity: O(n^3) – where n is the dimension of the matrices
+ * (assuming square matrices for simplicity).
+ *
+ *
Space Complexity: O(n^2) – for storing the result matrix.
+ *
+ *
+ * @author Nishitha Wihala Pitigala
+ *
+ */
+
+public final class MatrixMultiplication {
+ private MatrixMultiplication() {
+ }
+
+ /**
+ * Multiplies two matrices.
+ *
+ * @param matrixA the first matrix rowsA x colsA
+ * @param matrixB the second matrix rowsB x colsB
+ * @return the product of the two matrices rowsA x colsB
+ * @throws IllegalArgumentException if the matrices cannot be multiplied
+ */
+ public static double[][] multiply(double[][] matrixA, double[][] matrixB) {
+ // Check the input matrices are not null
+ if (matrixA == null || matrixB == null) {
+ throw new IllegalArgumentException("Input matrices cannot be null");
+ }
+
+ // Check for empty matrices
+ if (matrixA.length == 0 || matrixB.length == 0 || matrixA[0].length == 0 || matrixB[0].length == 0) {
+ throw new IllegalArgumentException("Input matrices must not be empty");
+ }
+
+ // Validate the matrix dimensions
+ if (matrixA[0].length != matrixB.length) {
+ throw new IllegalArgumentException("Matrices cannot be multiplied: incompatible dimensions.");
+ }
+
+ int rowsA = matrixA.length;
+ int colsA = matrixA[0].length;
+ int colsB = matrixB[0].length;
+
+ // Initialize the result matrix with zeros
+ double[][] result = new double[rowsA][colsB];
+
+ // Perform matrix multiplication
+ for (int i = 0; i < rowsA; i++) {
+ for (int j = 0; j < colsB; j++) {
+ for (int k = 0; k < colsA; k++) {
+ result[i][j] += matrixA[i][k] * matrixB[k][j];
+ }
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/matrix/MatrixMultiplicationTest.java b/src/test/java/com/thealgorithms/matrix/MatrixMultiplicationTest.java
new file mode 100644
index 000000000000..9463d33a18cb
--- /dev/null
+++ b/src/test/java/com/thealgorithms/matrix/MatrixMultiplicationTest.java
@@ -0,0 +1,101 @@
+package com.thealgorithms.matrix;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class MatrixMultiplicationTest {
+
+ private static final double EPSILON = 1e-9; // for floating point comparison
+
+ @Test
+ void testMultiply1by1() {
+ double[][] matrixA = {{1.0}};
+ double[][] matrixB = {{2.0}};
+ double[][] expected = {{2.0}};
+
+ double[][] result = MatrixMultiplication.multiply(matrixA, matrixB);
+ assertMatrixEquals(expected, result);
+ }
+
+ @Test
+ void testMultiply2by2() {
+ double[][] matrixA = {{1.0, 2.0}, {3.0, 4.0}};
+ double[][] matrixB = {{5.0, 6.0}, {7.0, 8.0}};
+ double[][] expected = {{19.0, 22.0}, {43.0, 50.0}};
+
+ double[][] result = MatrixMultiplication.multiply(matrixA, matrixB);
+ assertMatrixEquals(expected, result); // Use custom method due to floating point issues
+ }
+
+ @Test
+ void testMultiply3by2and2by1() {
+ double[][] matrixA = {{1.0, 2.0}, {3.0, 4.0}, {5.0, 6.0}};
+ double[][] matrixB = {{7.0}, {8.0}};
+ double[][] expected = {{23.0}, {53.0}, {83.0}};
+
+ double[][] result = MatrixMultiplication.multiply(matrixA, matrixB);
+ assertMatrixEquals(expected, result);
+ }
+
+ @Test
+ void testMultiplyNonRectangularMatrices() {
+ double[][] matrixA = {{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}};
+ double[][] matrixB = {{7.0, 8.0}, {9.0, 10.0}, {11.0, 12.0}};
+ double[][] expected = {{58.0, 64.0}, {139.0, 154.0}};
+
+ double[][] result = MatrixMultiplication.multiply(matrixA, matrixB);
+ assertMatrixEquals(expected, result);
+ }
+
+ @Test
+ void testNullMatrixA() {
+ double[][] b = {{1, 2}, {3, 4}};
+ assertThrows(IllegalArgumentException.class, () -> MatrixMultiplication.multiply(null, b));
+ }
+
+ @Test
+ void testNullMatrixB() {
+ double[][] a = {{1, 2}, {3, 4}};
+ assertThrows(IllegalArgumentException.class, () -> MatrixMultiplication.multiply(a, null));
+ }
+
+ @Test
+ void testMultiplyNull() {
+ double[][] matrixA = {{1.0, 2.0}, {3.0, 4.0}};
+ double[][] matrixB = null;
+
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> MatrixMultiplication.multiply(matrixA, matrixB));
+
+ String expectedMessage = "Input matrices cannot be null";
+ String actualMessage = exception.getMessage();
+
+ assertTrue(actualMessage.contains(expectedMessage));
+ }
+
+ @Test
+ void testIncompatibleDimensions() {
+ double[][] a = {{1.0, 2.0}};
+ double[][] b = {{1.0, 2.0}};
+ assertThrows(IllegalArgumentException.class, () -> MatrixMultiplication.multiply(a, b));
+ }
+
+ @Test
+ void testEmptyMatrices() {
+ double[][] a = new double[0][0];
+ double[][] b = new double[0][0];
+ assertThrows(IllegalArgumentException.class, () -> MatrixMultiplication.multiply(a, b));
+ }
+
+ private void assertMatrixEquals(double[][] expected, double[][] actual) {
+ assertEquals(expected.length, actual.length, "Row count mismatch");
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals(expected[i].length, actual[i].length, "Column count mismatch at row " + i);
+ for (int j = 0; j < expected[i].length; j++) {
+ assertEquals(expected[i][j], actual[i][j], EPSILON, "Mismatch at (" + i + "," + j + ")");
+ }
+ }
+ }
+}
From c9cc8f469881ef611ed8b43fcf39c51e2de6fa4d Mon Sep 17 00:00:00 2001
From: IDDQD
Date: Mon, 21 Jul 2025 19:11:13 +0300
Subject: [PATCH 31/57] Add cache with LIFO replacement policy (#6390)
* Added Random Replacement cache
* Implement cache with LIFO replacement policy
* Ran clang-format
* Make necessary variables final, replace HashMap.newHashMap(int capacity) with new HashMap<>(int capacity)
---------
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../datastructures/caches/LIFOCache.java | 563 ++++++++++++++++++
.../datastructures/caches/LIFOCacheTest.java | 341 +++++++++++
2 files changed, 904 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/datastructures/caches/LIFOCache.java
create mode 100644 src/test/java/com/thealgorithms/datastructures/caches/LIFOCacheTest.java
diff --git a/src/main/java/com/thealgorithms/datastructures/caches/LIFOCache.java b/src/main/java/com/thealgorithms/datastructures/caches/LIFOCache.java
new file mode 100644
index 000000000000..df3d4da912fe
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/caches/LIFOCache.java
@@ -0,0 +1,563 @@
+package com.thealgorithms.datastructures.caches;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.BiConsumer;
+
+/**
+ * A thread-safe generic cache implementation using the Last-In-First-Out eviction policy.
+ *
+ * The cache holds a fixed number of entries, defined by its capacity. When the cache is full and a
+ * new entry is added, the youngest entry in the cache is selected and evicted to make space.
+ *
+ * Optionally, entries can have a time-to-live (TTL) in milliseconds. If a TTL is set, entries will
+ * automatically expire and be removed upon access or insertion attempts.
+ *
+ * Features:
+ *
+ *
Removes youngest entry when capacity is exceeded
+ *
Optional TTL (time-to-live in milliseconds) per entry or default TTL for all entries
+ *
Thread-safe access using locking
+ *
Hit and miss counters for cache statistics
+ *
Eviction listener callback support
+ *
+ *
+ * @param the type of keys maintained by this cache
+ * @param the type of mapped values
+ * See LIFO
+ * @author Kevin Babu (GitHub)
+ */
+public final class LIFOCache {
+
+ private final int capacity;
+ private final long defaultTTL;
+ private final Map> cache;
+ private final Lock lock;
+ private final Deque keys;
+ private long hits = 0;
+ private long misses = 0;
+ private final BiConsumer evictionListener;
+ private final EvictionStrategy evictionStrategy;
+
+ /**
+ * Internal structure to store value + expiry timestamp.
+ *
+ * @param the type of the value being cached
+ */
+ private static class CacheEntry {
+ V value;
+ long expiryTime;
+
+ /**
+ * Constructs a new {@code CacheEntry} with the specified value and time-to-live (TTL).
+ * If TTL is 0, the entry is kept indefinitely, that is, unless it is the first value,
+ * then it will be removed according to the LIFO principle
+ *
+ * @param value the value to cache
+ * @param ttlMillis the time-to-live in milliseconds
+ */
+ CacheEntry(V value, long ttlMillis) {
+ this.value = value;
+ if (ttlMillis == 0) {
+ this.expiryTime = Long.MAX_VALUE;
+ } else {
+ this.expiryTime = System.currentTimeMillis() + ttlMillis;
+ }
+ }
+
+ /**
+ * Checks if the cache entry has expired.
+ *
+ * @return {@code true} if the current time is past the expiration time; {@code false} otherwise
+ */
+ boolean isExpired() {
+ return System.currentTimeMillis() > expiryTime;
+ }
+ }
+
+ /**
+ * Constructs a new {@code LIFOCache} instance using the provided {@link Builder}.
+ *
+ *
This constructor initializes the cache with the specified capacity and default TTL,
+ * sets up internal data structures (a {@code HashMap} for cache entries,
+ * {an @code ArrayDeque}, for key storage, and configures eviction.
+ *
+ * @param builder the {@code Builder} object containing configuration parameters
+ */
+ private LIFOCache(Builder builder) {
+ this.capacity = builder.capacity;
+ this.defaultTTL = builder.defaultTTL;
+ this.cache = new HashMap<>(builder.capacity);
+ this.keys = new ArrayDeque<>(builder.capacity);
+ this.lock = new ReentrantLock();
+ this.evictionListener = builder.evictionListener;
+ this.evictionStrategy = builder.evictionStrategy;
+ }
+
+ /**
+ * Retrieves the value associated with the specified key from the cache.
+ *
+ *
If the key is not present or the corresponding entry has expired, this method
+ * returns {@code null}. If an expired entry is found, it will be removed and the
+ * eviction listener (if any) will be notified. Cache hit-and-miss statistics are
+ * also updated accordingly.
+ *
+ * @param key the key whose associated value is to be returned; must not be {@code null}
+ * @return the cached value associated with the key, or {@code null} if not present or expired
+ * @throws IllegalArgumentException if {@code key} is {@code null}
+ */
+ public V get(K key) {
+ if (key == null) {
+ throw new IllegalArgumentException("Key must not be null");
+ }
+
+ lock.lock();
+ try {
+ evictionStrategy.onAccess(this);
+
+ final CacheEntry entry = cache.get(key);
+ if (entry == null || entry.isExpired()) {
+ if (entry != null) {
+ cache.remove(key);
+ keys.remove(key);
+ notifyEviction(key, entry.value);
+ }
+ misses++;
+ return null;
+ }
+ hits++;
+ return entry.value;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Adds a key-value pair to the cache using the default time-to-live (TTL).
+ *
+ *
The key may overwrite an existing entry. The actual insertion is delegated
+ * to the overloaded {@link #put(K, V, long)} method.
+ *
+ * @param key the key to cache the value under
+ * @param value the value to be cached
+ */
+ public void put(K key, V value) {
+ put(key, value, defaultTTL);
+ }
+
+ /**
+ * Adds a key-value pair to the cache with a specified time-to-live (TTL).
+ *
+ *
If the key already exists, its value is removed, re-inserted at tail and its TTL is reset.
+ * If the key does not exist and the cache is full, the youngest entry is evicted to make space.
+ * Expired entries are also cleaned up prior to any eviction. The eviction listener
+ * is notified when an entry gets evicted.
+ *
+ * @param key the key to associate with the cached value; must not be {@code null}
+ * @param value the value to be cached; must not be {@code null}
+ * @param ttlMillis the time-to-live for this entry in milliseconds; must be >= 0
+ * @throws IllegalArgumentException if {@code key} or {@code value} is {@code null}, or if {@code ttlMillis} is negative
+ */
+ public void put(K key, V value, long ttlMillis) {
+ if (key == null || value == null) {
+ throw new IllegalArgumentException("Key and value must not be null");
+ }
+ if (ttlMillis < 0) {
+ throw new IllegalArgumentException("TTL must be >= 0");
+ }
+
+ lock.lock();
+ try {
+ // If key already exists, remove it. It will later be re-inserted at top of stack
+ keys.remove(key);
+ final CacheEntry oldEntry = cache.remove(key);
+ if (oldEntry != null && !oldEntry.isExpired()) {
+ notifyEviction(key, oldEntry.value);
+ }
+
+ // Evict expired entries to make space for new entry
+ evictExpired();
+
+ // If no expired entry was removed, remove the youngest
+ if (cache.size() >= capacity) {
+ final K youngestKey = keys.pollLast();
+ final CacheEntry youngestEntry = cache.remove(youngestKey);
+ notifyEviction(youngestKey, youngestEntry.value);
+ }
+
+ // Insert new entry at tail
+ keys.add(key);
+ cache.put(key, new CacheEntry<>(value, ttlMillis));
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Removes all expired entries from the cache.
+ *
+ *
This method iterates through the list of cached keys and checks each associated
+ * entry for expiration. Expired entries are removed the cache map. For each eviction,
+ * the eviction listener is notified.
+ */
+ private int evictExpired() {
+ int count = 0;
+ final Iterator it = keys.iterator();
+
+ while (it.hasNext()) {
+ final K k = it.next();
+ final CacheEntry entry = cache.get(k);
+ if (entry != null && entry.isExpired()) {
+ it.remove();
+ cache.remove(k);
+ notifyEviction(k, entry.value);
+ count++;
+ }
+ }
+
+ return count;
+ }
+
+ /**
+ * Removes the specified key and its associated entry from the cache.
+ *
+ * @param key the key to remove from the cache;
+ * @return the value associated with the key; or {@code null} if no such key exists
+ */
+ public V removeKey(K key) {
+ if (key == null) {
+ throw new IllegalArgumentException("Key cannot be null");
+ }
+ lock.lock();
+ try {
+ final CacheEntry entry = cache.remove(key);
+ keys.remove(key);
+
+ // No such key in cache
+ if (entry == null) {
+ return null;
+ }
+
+ notifyEviction(key, entry.value);
+ return entry.value;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Notifies the eviction listener, if one is registered, that a key-value pair has been evicted.
+ *
+ *
If the {@code evictionListener} is not {@code null}, it is invoked with the provided key
+ * and value. Any exceptions thrown by the listener are caught and logged to standard error,
+ * preventing them from disrupting cache operations.
+ *
+ * @param key the key that was evicted
+ * @param value the value that was associated with the evicted key
+ */
+ private void notifyEviction(K key, V value) {
+ if (evictionListener != null) {
+ try {
+ evictionListener.accept(key, value);
+ } catch (Exception e) {
+ System.err.println("Eviction listener failed: " + e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Returns the number of successful cache lookups (hits).
+ *
+ * @return the number of cache hits
+ */
+ public long getHits() {
+ lock.lock();
+ try {
+ return hits;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Returns the number of failed cache lookups (misses), including expired entries.
+ *
+ * @return the number of cache misses
+ */
+ public long getMisses() {
+ lock.lock();
+ try {
+ return misses;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Returns the current number of entries in the cache, excluding expired ones.
+ *
+ * @return the current cache size
+ */
+ public int size() {
+ lock.lock();
+ try {
+ evictionStrategy.onAccess(this);
+
+ int count = 0;
+ for (CacheEntry entry : cache.values()) {
+ if (!entry.isExpired()) {
+ ++count;
+ }
+ }
+ return count;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Removes all entries from the cache, regardless of their expiration status.
+ *
+ *
This method clears the internal cache map entirely, resets the hit-and-miss counters,
+ * and notifies the eviction listener (if any) for each removed entry.
+ * Note that expired entries are treated the same as active ones for the purpose of clearing.
+ *
+ *
This operation acquires the internal lock to ensure thread safety.
+ */
+ public void clear() {
+ lock.lock();
+ try {
+ for (Map.Entry> entry : cache.entrySet()) {
+ notifyEviction(entry.getKey(), entry.getValue().value);
+ }
+ keys.clear();
+ cache.clear();
+ hits = 0;
+ misses = 0;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Returns a set of all keys currently stored in the cache that have not expired.
+ *
+ *
This method iterates through the cache and collects the keys of all non-expired entries.
+ * Expired entries are ignored but not removed. If you want to ensure expired entries are cleaned up,
+ * consider invoking {@link EvictionStrategy#onAccess(LIFOCache)} or calling {@link #evictExpired()} manually.
+ *
+ *
This operation acquires the internal lock to ensure thread safety.
+ *
+ * @return a set containing all non-expired keys currently in the cache
+ */
+ public Set getAllKeys() {
+ lock.lock();
+ try {
+ final Set result = new HashSet<>();
+
+ for (Map.Entry> entry : cache.entrySet()) {
+ if (!entry.getValue().isExpired()) {
+ result.add(entry.getKey());
+ }
+ }
+
+ return result;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Returns the current {@link EvictionStrategy} used by this cache instance.
+
+ * @return the eviction strategy currently assigned to this cache
+ */
+ public EvictionStrategy getEvictionStrategy() {
+ return evictionStrategy;
+ }
+
+ /**
+ * Returns a string representation of the cache, including metadata and current non-expired entries.
+ *
+ *
The returned string includes the cache's capacity, current size (excluding expired entries),
+ * hit-and-miss counts, and a map of all non-expired key-value pairs. This method acquires a lock
+ * to ensure thread-safe access.
+ *
+ * @return a string summarizing the state of the cache
+ */
+ @Override
+ public String toString() {
+ lock.lock();
+ try {
+ final Map visible = new LinkedHashMap<>();
+ for (Map.Entry> entry : cache.entrySet()) {
+ if (!entry.getValue().isExpired()) {
+ visible.put(entry.getKey(), entry.getValue().value);
+ }
+ }
+ return String.format("Cache(capacity=%d, size=%d, hits=%d, misses=%d, entries=%s)", capacity, visible.size(), hits, misses, visible);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * A strategy interface for controlling when expired entries are evicted from the cache.
+ *
+ *
Implementations decide whether and when to trigger {@link LIFOCache#evictExpired()} based
+ * on cache usage patterns. This allows for flexible eviction behaviour such as periodic cleanup,
+ * or no automatic cleanup.
+ *
+ * @param the type of keys maintained by the cache
+ * @param the type of cached values
+ */
+ public interface EvictionStrategy {
+ /**
+ * Called on each cache access (e.g., {@link LIFOCache#get(Object)}) to optionally trigger eviction.
+ *
+ * @param cache the cache instance on which this strategy is applied
+ * @return the number of expired entries evicted during this access
+ */
+ int onAccess(LIFOCache cache);
+ }
+
+ /**
+ * An eviction strategy that performs eviction of expired entries on each call.
+ *
+ * @param the type of keys
+ * @param the type of values
+ */
+ public static class ImmediateEvictionStrategy implements EvictionStrategy {
+ @Override
+ public int onAccess(LIFOCache cache) {
+ return cache.evictExpired();
+ }
+ }
+
+ /**
+ * An eviction strategy that triggers eviction on every fixed number of accesses.
+ *
+ *
This deterministic strategy ensures cleanup occurs at predictable intervals,
+ * ideal for moderately active caches where memory usage is a concern.
+ *
+ * @param the type of keys
+ * @param the type of values
+ */
+ public static class PeriodicEvictionStrategy implements EvictionStrategy {
+ private final int interval;
+ private final AtomicInteger counter = new AtomicInteger();
+
+ /**
+ * Constructs a periodic eviction strategy.
+ *
+ * @param interval the number of accesses between evictions; must be > 0
+ * @throws IllegalArgumentException if {@code interval} is less than or equal to 0
+ */
+ public PeriodicEvictionStrategy(int interval) {
+ if (interval <= 0) {
+ throw new IllegalArgumentException("Interval must be > 0");
+ }
+ this.interval = interval;
+ }
+
+ @Override
+ public int onAccess(LIFOCache cache) {
+ if (counter.incrementAndGet() % interval == 0) {
+ return cache.evictExpired();
+ }
+
+ return 0;
+ }
+ }
+
+ /**
+ * A builder for constructing a {@link LIFOCache} instance with customizable settings.
+ *
+ *
Allows configuring capacity, default TTL, eviction listener, and a pluggable eviction
+ * strategy. Call {@link #build()} to create the configured cache instance.
+ *
+ * @param the type of keys maintained by the cache
+ * @param the type of values stored in the cache
+ */
+ public static class Builder {
+ private final int capacity;
+ private long defaultTTL = 0;
+ private BiConsumer evictionListener;
+ private EvictionStrategy evictionStrategy = new LIFOCache.ImmediateEvictionStrategy<>();
+ /**
+ * Creates a new {@code Builder} with the specified cache capacity.
+ *
+ * @param capacity the maximum number of entries the cache can hold; must be > 0
+ * @throws IllegalArgumentException if {@code capacity} is less than or equal to 0
+ */
+ public Builder(int capacity) {
+ if (capacity <= 0) {
+ throw new IllegalArgumentException("Capacity must be > 0");
+ }
+ this.capacity = capacity;
+ }
+
+ /**
+ * Sets the default time-to-live (TTL) in milliseconds for cache entries.
+ *
+ * @param ttlMillis the TTL duration in milliseconds; must be >= 0
+ * @return this builder instance for chaining
+ * @throws IllegalArgumentException if {@code ttlMillis} is negative
+ */
+ public Builder defaultTTL(long ttlMillis) {
+ if (ttlMillis < 0) {
+ throw new IllegalArgumentException("Default TTL must be >= 0");
+ }
+ this.defaultTTL = ttlMillis;
+ return this;
+ }
+
+ /**
+ * Sets an eviction listener to be notified when entries are evicted from the cache.
+ *
+ * @param listener a {@link BiConsumer} that accepts evicted keys and values; must not be {@code null}
+ * @return this builder instance for chaining
+ * @throws IllegalArgumentException if {@code listener} is {@code null}
+ */
+ public Builder evictionListener(BiConsumer listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("Listener must not be null");
+ }
+ this.evictionListener = listener;
+ return this;
+ }
+
+ /**
+ * Builds and returns a new {@link LIFOCache} instance with the configured parameters.
+ *
+ * @return a fully configured {@code LIFOCache} instance
+ */
+ public LIFOCache build() {
+ return new LIFOCache<>(this);
+ }
+
+ /**
+ * Sets the eviction strategy used to determine when to clean up expired entries.
+ *
+ * @param strategy an {@link EvictionStrategy} implementation; must not be {@code null}
+ * @return this builder instance
+ * @throws IllegalArgumentException if {@code strategy} is {@code null}
+ */
+ public Builder evictionStrategy(EvictionStrategy strategy) {
+ if (strategy == null) {
+ throw new IllegalArgumentException("Eviction strategy must not be null");
+ }
+ this.evictionStrategy = strategy;
+ return this;
+ }
+ }
+}
diff --git a/src/test/java/com/thealgorithms/datastructures/caches/LIFOCacheTest.java b/src/test/java/com/thealgorithms/datastructures/caches/LIFOCacheTest.java
new file mode 100644
index 000000000000..df60a393b136
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/caches/LIFOCacheTest.java
@@ -0,0 +1,341 @@
+package com.thealgorithms.datastructures.caches;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
+
+class LIFOCacheTest {
+ private LIFOCache cache;
+ private Set evictedKeys;
+ private List evictedValues;
+
+ @BeforeEach
+ void setUp() {
+ evictedKeys = new HashSet<>();
+ evictedValues = new ArrayList<>();
+
+ cache = new LIFOCache.Builder(3)
+ .defaultTTL(1000)
+ .evictionListener((k, v) -> {
+ evictedKeys.add(k);
+ evictedValues.add(v);
+ })
+ .build();
+ }
+
+ @Test
+ void testPutAndGet() {
+ cache.put("a", "apple");
+ Assertions.assertEquals("apple", cache.get("a"));
+ }
+
+ @Test
+ void testOverwriteValue() {
+ cache.put("a", "apple");
+ cache.put("a", "avocado");
+ Assertions.assertEquals("avocado", cache.get("a"));
+ }
+
+ @Test
+ void testExpiration() throws InterruptedException {
+ cache.put("temp", "value", 100);
+ Thread.sleep(200);
+ Assertions.assertNull(cache.get("temp"));
+ Assertions.assertTrue(evictedKeys.contains("temp"));
+ }
+
+ @Test
+ void testEvictionOnCapacity() {
+ cache.put("a", "alpha");
+ cache.put("b", "bravo");
+ cache.put("c", "charlie");
+ cache.put("d", "delta");
+
+ int size = cache.size();
+ Assertions.assertEquals(3, size);
+ Assertions.assertEquals(1, evictedKeys.size());
+ Assertions.assertEquals(1, evictedValues.size());
+ Assertions.assertEquals("charlie", evictedValues.getFirst());
+ }
+
+ @Test
+ void testEvictionListener() {
+ cache.put("x", "one");
+ cache.put("y", "two");
+ cache.put("z", "three");
+ cache.put("w", "four");
+
+ Assertions.assertFalse(evictedKeys.isEmpty());
+ Assertions.assertFalse(evictedValues.isEmpty());
+ }
+
+ @Test
+ void testHitsAndMisses() {
+ cache.put("a", "apple");
+ Assertions.assertEquals("apple", cache.get("a"));
+ Assertions.assertNull(cache.get("b"));
+ Assertions.assertEquals(1, cache.getHits());
+ Assertions.assertEquals(1, cache.getMisses());
+ }
+
+ @Test
+ void testSizeExcludesExpired() throws InterruptedException {
+ cache.put("a", "a", 100);
+ cache.put("b", "b", 100);
+ cache.put("c", "c", 100);
+ Thread.sleep(150);
+ Assertions.assertEquals(0, cache.size());
+ }
+
+ @Test
+ void testSizeIncludesFresh() {
+ cache.put("a", "a", 1000);
+ cache.put("b", "b", 1000);
+ cache.put("c", "c", 1000);
+ Assertions.assertEquals(3, cache.size());
+ }
+
+ @Test
+ void testToStringDoesNotExposeExpired() throws InterruptedException {
+ cache.put("live", "alive");
+ cache.put("dead", "gone", 100);
+ Thread.sleep(150);
+ String result = cache.toString();
+ Assertions.assertTrue(result.contains("live"));
+ Assertions.assertFalse(result.contains("dead"));
+ }
+
+ @Test
+ void testNullKeyGetThrows() {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> cache.get(null));
+ }
+
+ @Test
+ void testPutNullKeyThrows() {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> cache.put(null, "v"));
+ }
+
+ @Test
+ void testPutNullValueThrows() {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> cache.put("k", null));
+ }
+
+ @Test
+ void testPutNegativeTTLThrows() {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> cache.put("k", "v", -1));
+ }
+
+ @Test
+ void testBuilderNegativeCapacityThrows() {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> new LIFOCache.Builder<>(0));
+ }
+
+ @Test
+ void testBuilderNullEvictionListenerThrows() {
+ LIFOCache.Builder builder = new LIFOCache.Builder<>(1);
+ Assertions.assertThrows(IllegalArgumentException.class, () -> builder.evictionListener(null));
+ }
+
+ @Test
+ void testEvictionListenerExceptionDoesNotCrash() {
+ LIFOCache listenerCache = new LIFOCache.Builder(1).evictionListener((k, v) -> { throw new RuntimeException("Exception"); }).build();
+
+ listenerCache.put("a", "a");
+ listenerCache.put("b", "b");
+ Assertions.assertDoesNotThrow(() -> listenerCache.get("a"));
+ }
+
+ @Test
+ void testTtlZeroThrowsIllegalArgumentException() {
+ Executable exec = () -> new LIFOCache.Builder(3).defaultTTL(-1).build();
+ Assertions.assertThrows(IllegalArgumentException.class, exec);
+ }
+
+ @Test
+ void testPeriodicEvictionStrategyEvictsAtInterval() throws InterruptedException {
+ LIFOCache periodicCache = new LIFOCache.Builder(10).defaultTTL(50).evictionStrategy(new LIFOCache.PeriodicEvictionStrategy<>(3)).build();
+
+ periodicCache.put("x", "1");
+ Thread.sleep(100);
+ int ev1 = periodicCache.getEvictionStrategy().onAccess(periodicCache);
+ int ev2 = periodicCache.getEvictionStrategy().onAccess(periodicCache);
+ int ev3 = periodicCache.getEvictionStrategy().onAccess(periodicCache);
+
+ Assertions.assertEquals(0, ev1);
+ Assertions.assertEquals(0, ev2);
+ Assertions.assertEquals(1, ev3, "Eviction should happen on the 3rd access");
+ Assertions.assertEquals(0, periodicCache.size());
+ }
+
+ @Test
+ void testPeriodicEvictionStrategyThrowsExceptionIfIntervalLessThanOrEqual0() {
+ Executable executable = () -> new LIFOCache.Builder(10).defaultTTL(50).evictionStrategy(new LIFOCache.PeriodicEvictionStrategy<>(0)).build();
+
+ Assertions.assertThrows(IllegalArgumentException.class, executable);
+ }
+
+ @Test
+ void testImmediateEvictionStrategyStrategyEvictsOnEachCall() throws InterruptedException {
+ LIFOCache immediateEvictionStrategy = new LIFOCache.Builder(10).defaultTTL(50).evictionStrategy(new LIFOCache.ImmediateEvictionStrategy<>()).build();
+
+ immediateEvictionStrategy.put("x", "1");
+ Thread.sleep(100);
+ int evicted = immediateEvictionStrategy.getEvictionStrategy().onAccess(immediateEvictionStrategy);
+
+ Assertions.assertEquals(1, evicted);
+ }
+
+ @Test
+ void testBuilderThrowsExceptionIfEvictionStrategyNull() {
+ Executable executable = () -> new LIFOCache.Builder(10).defaultTTL(50).evictionStrategy(null).build();
+
+ Assertions.assertThrows(IllegalArgumentException.class, executable);
+ }
+
+ @Test
+ void testReturnsCorrectStrategyInstance() {
+ LIFOCache.EvictionStrategy strategy = new LIFOCache.ImmediateEvictionStrategy<>();
+
+ LIFOCache newCache = new LIFOCache.Builder(10).defaultTTL(1000).evictionStrategy(strategy).build();
+
+ Assertions.assertSame(strategy, newCache.getEvictionStrategy(), "Returned strategy should be the same instance");
+ }
+
+ @Test
+ void testDefaultStrategyIsImmediateEvictionStrategy() {
+ LIFOCache newCache = new LIFOCache.Builder(5).defaultTTL(1000).build();
+
+ Assertions.assertInstanceOf(LIFOCache.ImmediateEvictionStrategy.class, newCache.getEvictionStrategy(), "Default strategy should be ImmediateEvictionStrategyStrategy");
+ }
+
+ @Test
+ void testGetEvictionStrategyIsNotNull() {
+ LIFOCache newCache = new LIFOCache.Builder(5).build();
+
+ Assertions.assertNotNull(newCache.getEvictionStrategy(), "Eviction strategy should never be null");
+ }
+
+ @Test
+ void testRemoveKeyRemovesExistingKey() {
+ cache.put("A", "Alpha");
+ cache.put("B", "Beta");
+
+ Assertions.assertEquals("Alpha", cache.get("A"));
+ Assertions.assertEquals("Beta", cache.get("B"));
+
+ String removed = cache.removeKey("A");
+ Assertions.assertEquals("Alpha", removed);
+
+ Assertions.assertNull(cache.get("A"));
+ Assertions.assertEquals(1, cache.size());
+ }
+
+ @Test
+ void testRemoveKeyReturnsNullIfKeyNotPresent() {
+ cache.put("X", "X-ray");
+
+ Assertions.assertNull(cache.removeKey("NonExistent"));
+ Assertions.assertEquals(1, cache.size());
+ }
+
+ @Test
+ void testRemoveKeyHandlesExpiredEntry() throws InterruptedException {
+ LIFOCache expiringCache = new LIFOCache.Builder(2).defaultTTL(100).evictionStrategy(new LIFOCache.ImmediateEvictionStrategy<>()).build();
+
+ expiringCache.put("T", "Temporary");
+
+ Thread.sleep(200);
+
+ String removed = expiringCache.removeKey("T");
+ Assertions.assertEquals("Temporary", removed);
+ Assertions.assertNull(expiringCache.get("T"));
+ }
+
+ @Test
+ void testRemoveKeyThrowsIfKeyIsNull() {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> cache.removeKey(null));
+ }
+
+ @Test
+ void testRemoveKeyTriggersEvictionListener() {
+ AtomicInteger evictedCount = new AtomicInteger();
+
+ LIFOCache localCache = new LIFOCache.Builder(2).evictionListener((key, value) -> evictedCount.incrementAndGet()).build();
+
+ localCache.put("A", "Apple");
+ localCache.put("B", "Banana");
+
+ localCache.removeKey("A");
+
+ Assertions.assertEquals(1, evictedCount.get(), "Eviction listener should have been called once");
+ }
+
+ @Test
+ void testRemoveKeyDoestNotAffectOtherKeys() {
+ cache.put("A", "Alpha");
+ cache.put("B", "Beta");
+ cache.put("C", "Gamma");
+
+ cache.removeKey("B");
+
+ Assertions.assertEquals("Alpha", cache.get("A"));
+ Assertions.assertNull(cache.get("B"));
+ Assertions.assertEquals("Gamma", cache.get("C"));
+ }
+
+ @Test
+ void testEvictionListenerExceptionDoesNotPropagate() {
+ LIFOCache localCache = new LIFOCache.Builder(1).evictionListener((key, value) -> { throw new RuntimeException(); }).build();
+
+ localCache.put("A", "Apple");
+
+ Assertions.assertDoesNotThrow(() -> localCache.put("B", "Beta"));
+ }
+
+ @Test
+ void testGetKeysReturnsAllFreshKeys() {
+ cache.put("A", "Alpha");
+ cache.put("B", "Beta");
+ cache.put("G", "Gamma");
+
+ Set expectedKeys = Set.of("A", "B", "G");
+ Assertions.assertEquals(expectedKeys, cache.getAllKeys());
+ }
+
+ @Test
+ void testGetKeysIgnoresExpiredKeys() throws InterruptedException {
+ cache.put("A", "Alpha");
+ cache.put("B", "Beta");
+ cache.put("G", "Gamma", 100);
+
+ Set expectedKeys = Set.of("A", "B");
+ Thread.sleep(200);
+ Assertions.assertEquals(expectedKeys, cache.getAllKeys());
+ }
+
+ @Test
+ void testClearRemovesAllEntries() {
+ cache.put("A", "Alpha");
+ cache.put("B", "Beta");
+ cache.put("G", "Gamma");
+
+ cache.clear();
+ Assertions.assertEquals(0, cache.size());
+ }
+
+ @Test
+ void testGetExpiredKeyIncrementsMissesCount() throws InterruptedException {
+ LIFOCache localCache = new LIFOCache.Builder(3).evictionStrategy(cache -> 0).defaultTTL(10).build();
+ localCache.put("A", "Alpha");
+ Thread.sleep(100);
+ String value = localCache.get("A");
+ Assertions.assertEquals(1, localCache.getMisses());
+ Assertions.assertNull(value);
+ }
+}
From 9a46339bec304158465aa497f7a062dbb7231307 Mon Sep 17 00:00:00 2001
From: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
Date: Mon, 21 Jul 2025 19:05:48 +0200
Subject: [PATCH 32/57] Update DIRECTORY.md (#6426)
Co-authored-by: DenizAltunkapan
---
DIRECTORY.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/DIRECTORY.md b/DIRECTORY.md
index cae5b8067c41..c52952edada4 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -132,6 +132,7 @@
- 📁 **caches**
- 📄 [FIFOCache](src/main/java/com/thealgorithms/datastructures/caches/FIFOCache.java)
- 📄 [LFUCache](src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java)
+ - 📄 [LIFOCache](src/main/java/com/thealgorithms/datastructures/caches/LIFOCache.java)
- 📄 [LRUCache](src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java)
- 📄 [MRUCache](src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java)
- 📄 [RRCache](src/main/java/com/thealgorithms/datastructures/caches/RRCache.java)
@@ -488,6 +489,7 @@
- 📄 [Volume](src/main/java/com/thealgorithms/maths/Volume.java)
- 📁 **matrix**
- 📄 [InverseOfMatrix](src/main/java/com/thealgorithms/matrix/InverseOfMatrix.java)
+ - 📄 [MatrixMultiplication](src/main/java/com/thealgorithms/matrix/MatrixMultiplication.java)
- 📄 [MatrixRank](src/main/java/com/thealgorithms/matrix/MatrixRank.java)
- 📄 [MatrixTranspose](src/main/java/com/thealgorithms/matrix/MatrixTranspose.java)
- 📄 [MedianOfMatrix](src/main/java/com/thealgorithms/matrix/MedianOfMatrix.java)
@@ -869,6 +871,7 @@
- 📁 **caches**
- 📄 [FIFOCacheTest](src/test/java/com/thealgorithms/datastructures/caches/FIFOCacheTest.java)
- 📄 [LFUCacheTest](src/test/java/com/thealgorithms/datastructures/caches/LFUCacheTest.java)
+ - 📄 [LIFOCacheTest](src/test/java/com/thealgorithms/datastructures/caches/LIFOCacheTest.java)
- 📄 [LRUCacheTest](src/test/java/com/thealgorithms/datastructures/caches/LRUCacheTest.java)
- 📄 [MRUCacheTest](src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java)
- 📄 [RRCacheTest](src/test/java/com/thealgorithms/datastructures/caches/RRCacheTest.java)
@@ -1177,6 +1180,7 @@
- 📄 [PrimeFactorizationTest](src/test/java/com/thealgorithms/maths/prime/PrimeFactorizationTest.java)
- 📁 **matrix**
- 📄 [InverseOfMatrixTest](src/test/java/com/thealgorithms/matrix/InverseOfMatrixTest.java)
+ - 📄 [MatrixMultiplicationTest](src/test/java/com/thealgorithms/matrix/MatrixMultiplicationTest.java)
- 📄 [MatrixRankTest](src/test/java/com/thealgorithms/matrix/MatrixRankTest.java)
- 📄 [MatrixTransposeTest](src/test/java/com/thealgorithms/matrix/MatrixTransposeTest.java)
- 📄 [MatrixUtilTest](src/test/java/com/thealgorithms/matrix/MatrixUtilTest.java)
From 78b62191ab09c566262b7a78d995def74d8b08e8 Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Tue, 22 Jul 2025 18:49:37 +0200
Subject: [PATCH 33/57] testing: improving `GenerateSubsetsTest` (#6412)
* testing: improving GenerateSubsetsTest
* testing: change List to more common Iterable
---------
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../recursion/GenerateSubsetsTest.java | 38 ++++++++++---------
1 file changed, 21 insertions(+), 17 deletions(-)
diff --git a/src/test/java/com/thealgorithms/recursion/GenerateSubsetsTest.java b/src/test/java/com/thealgorithms/recursion/GenerateSubsetsTest.java
index b92d1406b0a7..983552722781 100644
--- a/src/test/java/com/thealgorithms/recursion/GenerateSubsetsTest.java
+++ b/src/test/java/com/thealgorithms/recursion/GenerateSubsetsTest.java
@@ -1,36 +1,40 @@
package com.thealgorithms.recursion;
-import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertIterableEquals;
+import java.util.Arrays;
import java.util.List;
+import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public final class GenerateSubsetsTest {
@Test
- void subsetRecursionTestOne() {
- String str = "abc";
- String[] expected = new String[] {"abc", "ab", "ac", "a", "bc", "b", "c", ""};
-
- List ans = GenerateSubsets.subsetRecursion(str);
- assertArrayEquals(ans.toArray(), expected);
+ @DisplayName("Subsets of 'abc'")
+ void testSubsetsOfABC() {
+ assertSubsets("abc", Arrays.asList("abc", "ab", "ac", "a", "bc", "b", "c", ""));
}
@Test
- void subsetRecursionTestTwo() {
- String str = "cbf";
- String[] expected = new String[] {"cbf", "cb", "cf", "c", "bf", "b", "f", ""};
+ @DisplayName("Subsets of 'cbf'")
+ void testSubsetsOfCBF() {
+ assertSubsets("cbf", Arrays.asList("cbf", "cb", "cf", "c", "bf", "b", "f", ""));
+ }
- List ans = GenerateSubsets.subsetRecursion(str);
- assertArrayEquals(ans.toArray(), expected);
+ @Test
+ @DisplayName("Subsets of 'aba' with duplicates")
+ void testSubsetsWithDuplicateChars() {
+ assertSubsets("aba", Arrays.asList("aba", "ab", "aa", "a", "ba", "b", "a", ""));
}
@Test
- void subsetRecursionTestThree() {
- String str = "aba";
- String[] expected = new String[] {"aba", "ab", "aa", "a", "ba", "b", "a", ""};
+ @DisplayName("Subsets of empty string")
+ void testEmptyInput() {
+ assertSubsets("", List.of(""));
+ }
- List ans = GenerateSubsets.subsetRecursion(str);
- assertArrayEquals(ans.toArray(), expected);
+ private void assertSubsets(String input, Iterable expected) {
+ List actual = GenerateSubsets.subsetRecursion(input);
+ assertIterableEquals(expected, actual, "Subsets do not match for input: " + input);
}
}
From bbbc1dd9462e6de392e7dac192a6cac974e7ffd2 Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Tue, 22 Jul 2025 18:52:33 +0200
Subject: [PATCH 34/57] testing: improving CountSinglyLinkedListRecursionTest
(#6413)
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../CountSinglyLinkedListRecursionTest.java | 80 ++++++++++++++-----
1 file changed, 62 insertions(+), 18 deletions(-)
diff --git a/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java b/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java
index 1d814d0c2f9f..3d3f62fc5132 100644
--- a/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java
@@ -1,8 +1,10 @@
package com.thealgorithms.datastructures.lists;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class CountSinglyLinkedListRecursionTest {
@@ -15,70 +17,112 @@ public void setUp() {
}
@Test
+ @DisplayName("Count of an empty list should be 0")
public void testCountEmptyList() {
- // An empty list should have a count of 0
- assertEquals(0, list.count(), "Count of an empty list should be 0.");
+ assertEquals(0, list.count());
}
@Test
+ @DisplayName("Count after inserting a single element should be 1")
public void testCountSingleElementList() {
- // Insert a single element and check the count
list.insert(1);
- assertEquals(1, list.count(), "Count of a single-element list should be 1.");
+ assertEquals(1, list.count());
}
@Test
+ @DisplayName("Count after inserting multiple distinct elements")
public void testCountMultipleElements() {
- // Insert multiple elements and check the count
for (int i = 1; i <= 5; i++) {
list.insert(i);
}
- assertEquals(5, list.count(), "Count of a list with 5 elements should be 5.");
+ assertEquals(5, list.count());
}
@Test
+ @DisplayName("Count should reflect total number of nodes with duplicate values")
public void testCountWithDuplicateElements() {
- // Insert duplicate elements and verify the count is correct
- list.insert(1);
list.insert(2);
list.insert(2);
list.insert(3);
list.insert(3);
- assertEquals(5, list.count(), "Count of a list with duplicate elements should match total node count.");
+ list.insert(1);
+ assertEquals(5, list.count());
}
@Test
+ @DisplayName("Count should return 0 after clearing the list")
public void testCountAfterClearingList() {
for (int i = 1; i <= 4; i++) {
list.insert(i);
}
- list.clear(); // assuming you have a clear method; if not, skip this
- assertEquals(0, list.count(), "Count after clearing the list should be 0.");
+ list.clear(); // assumed to exist
+ assertEquals(0, list.count());
}
@Test
+ @DisplayName("Count on a very large list should be accurate")
public void testCountOnVeryLargeList() {
int n = 1000;
for (int i = 0; i < n; i++) {
list.insert(i);
}
- assertEquals(n, list.count(), "Count should correctly return for large list sizes.");
+ assertEquals(n, list.count());
}
@Test
+ @DisplayName("Count should work correctly with negative values")
public void testCountOnListWithNegativeNumbers() {
list.insert(-1);
- list.insert(-5);
- list.insert(-10);
- assertEquals(3, list.count(), "Count should correctly handle negative values.");
+ list.insert(-2);
+ list.insert(-3);
+ assertEquals(3, list.count());
}
@Test
+ @DisplayName("Calling count multiple times should return the same value if list is unchanged")
public void testCountIsConsistentWithoutModification() {
list.insert(1);
list.insert(2);
- int firstCount = list.count();
- int secondCount = list.count();
- assertEquals(firstCount, secondCount, "Repeated count calls should return consistent values.");
+ int count1 = list.count();
+ int count2 = list.count();
+ assertEquals(count1, count2);
+ }
+
+ @Test
+ @DisplayName("Count should reflect total even if all values are the same")
+ public void testCountAllSameValues() {
+ for (int i = 0; i < 5; i++) {
+ list.insert(42);
+ }
+ assertEquals(5, list.count());
+ }
+
+ @Test
+ @DisplayName("Count should remain correct after multiple interleaved insert and count operations")
+ public void testCountAfterEachInsert() {
+ assertEquals(0, list.count());
+ list.insert(1);
+ assertEquals(1, list.count());
+ list.insert(2);
+ assertEquals(2, list.count());
+ list.insert(3);
+ assertEquals(3, list.count());
+ }
+
+ @Test
+ @DisplayName("List should not throw on edge count (0 nodes)")
+ public void testEdgeCaseNoElements() {
+ assertDoesNotThrow(() -> list.count());
+ }
+
+ @Test
+ @DisplayName("Should count accurately after inserting then removing all elements")
+ public void testCountAfterInsertAndClear() {
+ for (int i = 0; i < 10; i++) {
+ list.insert(i);
+ }
+ assertEquals(10, list.count());
+ list.clear();
+ assertEquals(0, list.count());
}
}
From cfd784105bbb47b172c0ba65b46bd30700953c9f Mon Sep 17 00:00:00 2001
From: Vishwajeet Deshmane
Date: Tue, 22 Jul 2025 22:47:24 +0530
Subject: [PATCH 35/57] Feat(Improved): Add 0/1 Knapsack Problem: Recursive and
Tabulation (Bottom-Up DP) Implementations in Java along with their
corresponding Tests (#6425)
* feat: Add 0/1 Knapsack and its tabulation implementation with their corresponding tests
* feat: Add 0/1 Knapsack and its tabulation implementation with their corresponding tests
* feat: Add 0/1 Knapsack and its tabulation implementation with their corresponding tests
* Feat:add 0/1knapsack and 0/1knapsacktabulation along with their tests
* Feat:add 0/1knapsack and 0/1knapsacktabulation along with their tests
* Feat:add 0/1knapsack and 0/1knapsacktabulation along with their tests
---------
Co-authored-by: Oleksandr Klymenko
---
.../dynamicprogramming/KnapsackZeroOne.java | 53 +++++++++++++
.../KnapsackZeroOneTabulation.java | 69 ++++++++++++++++
.../KnapsackZeroOneTabulationTest.java | 78 +++++++++++++++++++
.../KnapsackZeroOneTest.java | 68 ++++++++++++++++
4 files changed, 268 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOne.java
create mode 100644 src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulation.java
create mode 100644 src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulationTest.java
create mode 100644 src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTest.java
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOne.java b/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOne.java
new file mode 100644
index 000000000000..abc1e321ca8f
--- /dev/null
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOne.java
@@ -0,0 +1,53 @@
+package com.thealgorithms.dynamicprogramming;
+
+/**
+ * The {@code KnapsackZeroOne} provides Recursive solution for the 0/1 Knapsack
+ * problem. Solves by exploring all combinations of items using recursion. No
+ * memoization or dynamic programming optimizations are applied.
+ *
+ * Time Complexity: O(2^n) — explores all subsets.
+ * Space Complexity: O(n) — due to recursive call stack.
+ *
+ * Problem Reference: https://en.wikipedia.org/wiki/Knapsack_problem
+ */
+public final class KnapsackZeroOne {
+
+ private KnapsackZeroOne() {
+ // Prevent instantiation
+ }
+
+ /**
+ * Solves the 0/1 Knapsack problem using recursion.
+ *
+ * @param values the array containing values of the items
+ * @param weights the array containing weights of the items
+ * @param capacity the total capacity of the knapsack
+ * @param n the number of items
+ * @return the maximum total value achievable within the given weight limit
+ * @throws IllegalArgumentException if input arrays are null, empty, or
+ * lengths mismatch
+ */
+ public static int compute(final int[] values, final int[] weights, final int capacity, final int n) {
+ if (values == null || weights == null) {
+ throw new IllegalArgumentException("Input arrays cannot be null.");
+ }
+ if (values.length != weights.length) {
+ throw new IllegalArgumentException("Value and weight arrays must be of the same length.");
+ }
+ if (capacity < 0 || n < 0) {
+ throw new IllegalArgumentException("Invalid input: arrays must be non-empty and capacity/n "
+ + "non-negative.");
+ }
+ if (n == 0 || capacity == 0 || values.length == 0) {
+ return 0;
+ }
+
+ if (weights[n - 1] <= capacity) {
+ final int include = values[n - 1] + compute(values, weights, capacity - weights[n - 1], n - 1);
+ final int exclude = compute(values, weights, capacity, n - 1);
+ return Math.max(include, exclude);
+ } else {
+ return compute(values, weights, capacity, n - 1);
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulation.java b/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulation.java
new file mode 100644
index 000000000000..c560efc61c71
--- /dev/null
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulation.java
@@ -0,0 +1,69 @@
+package com.thealgorithms.dynamicprogramming;
+
+/**
+ * Tabulation (Bottom-Up) Solution for 0-1 Knapsack Problem.
+ * This method uses dynamic programming to build up a solution iteratively,
+ * filling a 2-D array where each entry dp[i][w] represents the maximum value
+ * achievable with the first i items and a knapsack capacity of w.
+ *
+ * The tabulation approach is efficient because it avoids redundant calculations
+ * by solving all subproblems in advance and storing their results, ensuring
+ * each subproblem is solved only once. This is a key technique in dynamic programming,
+ * making it possible to solve problems that would otherwise be infeasible due to
+ * exponential time complexity in naive recursive solutions.
+ *
+ * Time Complexity: O(n * W), where n is the number of items and W is the knapsack capacity.
+ * Space Complexity: O(n * W) for the DP table.
+ *
+ * For more information, see:
+ * https://en.wikipedia.org/wiki/Knapsack_problem#Dynamic_programming
+ */
+public final class KnapsackZeroOneTabulation {
+
+ private KnapsackZeroOneTabulation() {
+ // Prevent instantiation
+ }
+
+ /**
+ * Solves the 0-1 Knapsack problem using the bottom-up tabulation technique.
+ * @param values the values of the items
+ * @param weights the weights of the items
+ * @param capacity the total capacity of the knapsack
+ * @param itemCount the number of items
+ * @return the maximum value that can be put in the knapsack
+ * @throws IllegalArgumentException if input arrays are null, of different lengths,or if capacity or itemCount is invalid
+ */
+ public static int compute(final int[] values, final int[] weights, final int capacity, final int itemCount) {
+ if (values == null || weights == null) {
+ throw new IllegalArgumentException("Values and weights arrays must not be null.");
+ }
+ if (values.length != weights.length) {
+ throw new IllegalArgumentException("Values and weights arrays must be non-null and of same length.");
+ }
+ if (capacity < 0) {
+ throw new IllegalArgumentException("Capacity must not be negative.");
+ }
+ if (itemCount < 0 || itemCount > values.length) {
+ throw new IllegalArgumentException("Item count must be between 0 and the length of the values array.");
+ }
+
+ final int[][] dp = new int[itemCount + 1][capacity + 1];
+
+ for (int i = 1; i <= itemCount; i++) {
+ final int currentValue = values[i - 1];
+ final int currentWeight = weights[i - 1];
+
+ for (int w = 1; w <= capacity; w++) {
+ if (currentWeight <= w) {
+ final int includeItem = currentValue + dp[i - 1][w - currentWeight];
+ final int excludeItem = dp[i - 1][w];
+ dp[i][w] = Math.max(includeItem, excludeItem);
+ } else {
+ dp[i][w] = dp[i - 1][w];
+ }
+ }
+ }
+
+ return dp[itemCount][capacity];
+ }
+}
diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulationTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulationTest.java
new file mode 100644
index 000000000000..6c61ad2130e3
--- /dev/null
+++ b/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulationTest.java
@@ -0,0 +1,78 @@
+package com.thealgorithms.dynamicprogramming;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class KnapsackZeroOneTabulationTest {
+
+ @Test
+ public void basicCheck() {
+ int[] values = {60, 100, 120};
+ int[] weights = {10, 20, 30};
+ int capacity = 50;
+ int itemCount = values.length;
+
+ int expected = 220; // Best choice: item 1 (100) and item 2 (120)
+ int result = KnapsackZeroOneTabulation.compute(values, weights, capacity, itemCount);
+ assertEquals(expected, result);
+ }
+
+ @Test
+ public void emptyKnapsack() {
+ int[] values = {};
+ int[] weights = {};
+ int capacity = 50;
+ int itemCount = 0;
+
+ assertEquals(0, KnapsackZeroOneTabulation.compute(values, weights, capacity, itemCount));
+ }
+
+ @Test
+ public void zeroCapacity() {
+ int[] values = {60, 100, 120};
+ int[] weights = {10, 20, 30};
+ int capacity = 0;
+ int itemCount = values.length;
+
+ assertEquals(0, KnapsackZeroOneTabulation.compute(values, weights, capacity, itemCount));
+ }
+
+ @Test
+ public void negativeCapacity() {
+ int[] values = {10, 20, 30};
+ int[] weights = {1, 1, 1};
+ int capacity = -10;
+ int itemCount = values.length;
+
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOneTabulation.compute(values, weights, capacity, itemCount));
+ assertEquals("Capacity must not be negative.", exception.getMessage());
+ }
+
+ @Test
+ public void mismatchedLengths() {
+ int[] values = {60, 100}; // Only 2 values
+ int[] weights = {10, 20, 30}; // 3 weights
+ int capacity = 50;
+ int itemCount = 2; // Matches `values.length`
+
+ // You could either expect 0 or throw an IllegalArgumentException in your compute function
+ assertThrows(IllegalArgumentException.class, () -> { KnapsackZeroOneTabulation.compute(values, weights, capacity, itemCount); });
+ }
+
+ @Test
+ public void nullInputs() {
+ int[] weights = {1, 2, 3};
+ int capacity = 10;
+ int itemCount = 3;
+
+ IllegalArgumentException exception1 = assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOneTabulation.compute(null, weights, capacity, itemCount));
+ assertEquals("Values and weights arrays must not be null.", exception1.getMessage());
+
+ int[] values = {1, 2, 3};
+
+ IllegalArgumentException exception2 = assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOneTabulation.compute(values, null, capacity, itemCount));
+ assertEquals("Values and weights arrays must not be null.", exception2.getMessage());
+ }
+}
diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTest.java
new file mode 100644
index 000000000000..0ca2b91fc273
--- /dev/null
+++ b/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTest.java
@@ -0,0 +1,68 @@
+package com.thealgorithms.dynamicprogramming;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+class KnapsackZeroOneTest {
+
+ @Test
+ void basicCheck() {
+ int[] values = {60, 100, 120};
+ int[] weights = {10, 20, 30};
+ int capacity = 50;
+ int expected = 220;
+
+ int result = KnapsackZeroOne.compute(values, weights, capacity, values.length);
+ assertEquals(expected, result);
+ }
+
+ @Test
+ void zeroCapacity() {
+ int[] values = {10, 20, 30};
+ int[] weights = {1, 1, 1};
+ int capacity = 0;
+
+ int result = KnapsackZeroOne.compute(values, weights, capacity, values.length);
+ assertEquals(0, result);
+ }
+
+ @Test
+ void zeroItems() {
+ int[] values = {};
+ int[] weights = {};
+ int capacity = 10;
+
+ int result = KnapsackZeroOne.compute(values, weights, capacity, 0);
+ assertEquals(0, result);
+ }
+
+ @Test
+ void weightsExceedingCapacity() {
+ int[] values = {10, 20};
+ int[] weights = {100, 200};
+ int capacity = 50;
+
+ int result = KnapsackZeroOne.compute(values, weights, capacity, values.length);
+ assertEquals(0, result);
+ }
+
+ @Test
+ void throwsOnNullArrays() {
+ assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOne.compute(null, new int[] {1}, 10, 1));
+ assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOne.compute(new int[] {1}, null, 10, 1));
+ }
+
+ @Test
+ void throwsOnMismatchedArrayLengths() {
+ assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOne.compute(new int[] {10, 20}, new int[] {5}, 15, 2));
+ }
+
+ @Test
+ void throwsOnNegativeInputs() {
+ assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOne.compute(new int[] {10}, new int[] {5}, -1, 1));
+
+ assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOne.compute(new int[] {10}, new int[] {5}, 5, -1));
+ }
+}
From ceead5eccd62b5e08e95bd838d731f08b685fdcb Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Tue, 22 Jul 2025 19:22:08 +0200
Subject: [PATCH 36/57] testing: refactor to ParameterizedTest
`PrefixEvaluatorTest` (#6415)
testing: refactor to ParameterizedTest PrefixEvaluatorTest
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../stacks/PrefixEvaluatorTest.java | 18 +++++++++++-------
1 file changed, 11 insertions(+), 7 deletions(-)
diff --git a/src/test/java/com/thealgorithms/stacks/PrefixEvaluatorTest.java b/src/test/java/com/thealgorithms/stacks/PrefixEvaluatorTest.java
index e2faa61955b3..ba67163fd49e 100644
--- a/src/test/java/com/thealgorithms/stacks/PrefixEvaluatorTest.java
+++ b/src/test/java/com/thealgorithms/stacks/PrefixEvaluatorTest.java
@@ -4,24 +4,28 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.EmptyStackException;
+import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
public class PrefixEvaluatorTest {
- @Test
- public void testValidExpressions() {
- assertEquals(10, PrefixEvaluator.evaluatePrefix("+ * 2 3 4"));
- assertEquals(5, PrefixEvaluator.evaluatePrefix("- + 7 3 5"));
- assertEquals(6, PrefixEvaluator.evaluatePrefix("/ * 3 2 1"));
+ @ParameterizedTest(name = "Expression: \"{0}\" → Result: {1}")
+ @CsvSource({"'+ * 2 3 4', 10", "'- + 7 3 5', 5", "'/ * 3 2 1', 6"})
+ void testValidExpressions(String expression, int expected) {
+ assertEquals(expected, PrefixEvaluator.evaluatePrefix(expression));
}
@Test
- public void testInvalidExpression() {
+ @DisplayName("Should throw EmptyStackException for incomplete expression")
+ void testInvalidExpression() {
assertThrows(EmptyStackException.class, () -> PrefixEvaluator.evaluatePrefix("+ 3"));
}
@Test
- public void testMoreThanOneStackSizeAfterEvaluation() {
+ @DisplayName("Should throw IllegalArgumentException if stack not reduced to one result")
+ void testMoreThanOneStackSizeAfterEvaluation() {
assertThrows(IllegalArgumentException.class, () -> PrefixEvaluator.evaluatePrefix("+ 3 4 5"));
}
}
From 7c2af29d295adf225359326fb75b4e56dd34286f Mon Sep 17 00:00:00 2001
From: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
Date: Tue, 22 Jul 2025 19:25:46 +0200
Subject: [PATCH 37/57] Update DIRECTORY.md (#6431)
Co-authored-by: alxkm
---
DIRECTORY.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/DIRECTORY.md b/DIRECTORY.md
index c52952edada4..9067f17ebc2d 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -304,6 +304,8 @@
- 📄 [KadaneAlgorithm](src/main/java/com/thealgorithms/dynamicprogramming/KadaneAlgorithm.java)
- 📄 [Knapsack](src/main/java/com/thealgorithms/dynamicprogramming/Knapsack.java)
- 📄 [KnapsackMemoization](src/main/java/com/thealgorithms/dynamicprogramming/KnapsackMemoization.java)
+ - 📄 [KnapsackZeroOne](src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOne.java)
+ - 📄 [KnapsackZeroOneTabulation](src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulation.java)
- 📄 [LevenshteinDistance](src/main/java/com/thealgorithms/dynamicprogramming/LevenshteinDistance.java)
- 📄 [LongestAlternatingSubsequence](src/main/java/com/thealgorithms/dynamicprogramming/LongestAlternatingSubsequence.java)
- 📄 [LongestArithmeticSubsequence](src/main/java/com/thealgorithms/dynamicprogramming/LongestArithmeticSubsequence.java)
@@ -1009,6 +1011,8 @@
- 📄 [KadaneAlgorithmTest](src/test/java/com/thealgorithms/dynamicprogramming/KadaneAlgorithmTest.java)
- 📄 [KnapsackMemoizationTest](src/test/java/com/thealgorithms/dynamicprogramming/KnapsackMemoizationTest.java)
- 📄 [KnapsackTest](src/test/java/com/thealgorithms/dynamicprogramming/KnapsackTest.java)
+ - 📄 [KnapsackZeroOneTabulationTest](src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulationTest.java)
+ - 📄 [KnapsackZeroOneTest](src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTest.java)
- 📄 [LevenshteinDistanceTests](src/test/java/com/thealgorithms/dynamicprogramming/LevenshteinDistanceTests.java)
- 📄 [LongestAlternatingSubsequenceTest](src/test/java/com/thealgorithms/dynamicprogramming/LongestAlternatingSubsequenceTest.java)
- 📄 [LongestArithmeticSubsequenceTest](src/test/java/com/thealgorithms/dynamicprogramming/LongestArithmeticSubsequenceTest.java)
From 3304cf2e58ff2e155dcfda9f465f2eb06e02c136 Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Tue, 22 Jul 2025 19:33:55 +0200
Subject: [PATCH 38/57] testing: improve `QueueByTwoStacksTest` (#6416)
testing: improve QueueByTwoStacksTest
---
.../queues/QueueByTwoStacksTest.java | 86 +++++++++++++++++--
1 file changed, 77 insertions(+), 9 deletions(-)
diff --git a/src/test/java/com/thealgorithms/datastructures/queues/QueueByTwoStacksTest.java b/src/test/java/com/thealgorithms/datastructures/queues/QueueByTwoStacksTest.java
index 87f136a84631..491cb7634302 100644
--- a/src/test/java/com/thealgorithms/datastructures/queues/QueueByTwoStacksTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/queues/QueueByTwoStacksTest.java
@@ -33,19 +33,19 @@ public void testDequeue() {
queue.put(10);
queue.put(20);
queue.put(30);
- assertEquals(10, queue.get()); // First item out
- assertEquals(20, queue.get()); // Second item out
- assertEquals(30, queue.get()); // Third item out
+ assertEquals(10, queue.get());
+ assertEquals(20, queue.get());
+ assertEquals(30, queue.get());
}
@Test
public void testInterleavedOperations() {
queue.put(10);
queue.put(20);
- assertEquals(10, queue.get()); // Dequeue first item
+ assertEquals(10, queue.get());
queue.put(30);
- assertEquals(20, queue.get()); // Dequeue second item
- assertEquals(30, queue.get()); // Dequeue third item
+ assertEquals(20, queue.get());
+ assertEquals(30, queue.get());
}
@Test
@@ -62,8 +62,76 @@ public void testQueueSize() {
@Test
public void testEmptyQueueException() {
- assertThrows(NoSuchElementException.class, () -> {
- queue.get(); // Attempting to dequeue from empty queue
- });
+ assertThrows(NoSuchElementException.class, () -> queue.get());
+ }
+
+ @Test
+ public void testDequeueAllElements() {
+ for (int i = 1; i <= 5; i++) {
+ queue.put(i);
+ }
+ for (int i = 1; i <= 5; i++) {
+ assertEquals(i, queue.get());
+ }
+ assertEquals(0, queue.size());
+ }
+
+ @Test
+ public void testLargeNumberOfOperations() {
+ int n = 1000;
+ for (int i = 0; i < n; i++) {
+ queue.put(i);
+ }
+ for (int i = 0; i < n; i++) {
+ assertEquals(i, queue.get());
+ }
+ assertEquals(0, queue.size());
+ }
+
+ @Test
+ public void testRefillDuringDequeue() {
+ queue.put(1);
+ queue.put(2);
+ assertEquals(1, queue.get());
+ queue.put(3);
+ queue.put(4);
+ assertEquals(2, queue.get());
+ assertEquals(3, queue.get());
+ assertEquals(4, queue.get());
+ }
+
+ @Test
+ public void testAlternatingPutAndGet() {
+ queue.put(1);
+ assertEquals(1, queue.get());
+ queue.put(2);
+ queue.put(3);
+ assertEquals(2, queue.get());
+ queue.put(4);
+ assertEquals(3, queue.get());
+ assertEquals(4, queue.get());
+ }
+
+ @Test
+ public void testSizeStability() {
+ queue.put(100);
+ int size1 = queue.size();
+ int size2 = queue.size();
+ assertEquals(size1, size2);
+ }
+
+ @Test
+ public void testMultipleEmptyDequeues() {
+ assertThrows(NoSuchElementException.class, () -> queue.get());
+ assertThrows(NoSuchElementException.class, () -> queue.get());
+ }
+
+ @Test
+ public void testQueueWithStrings() {
+ QueueByTwoStacks stringQueue = new QueueByTwoStacks<>();
+ stringQueue.put("a");
+ stringQueue.put("b");
+ assertEquals("a", stringQueue.get());
+ assertEquals("b", stringQueue.get());
}
}
From bcfb3f22e6f9b986c3bfdcfc26e1676c6f3a767a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 22 Jul 2025 23:39:53 +0200
Subject: [PATCH 39/57] chore(deps): bump org.junit:junit-bom from 5.13.3 to
5.13.4 (#6434)
Bumps [org.junit:junit-bom](https://github.com/junit-team/junit-framework) from 5.13.3 to 5.13.4.
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r5.13.3...r5.13.4)
---
updated-dependencies:
- dependency-name: org.junit:junit-bom
dependency-version: 5.13.4
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index f18462bec8fb..eff6b935e0fc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@
org.junitjunit-bom
- 5.13.3
+ 5.13.4pomimport
From 073b6f15a8ec177a2d95f69330985d5a0cc05611 Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko
Date: Wed, 23 Jul 2025 07:44:38 +0200
Subject: [PATCH 40/57] testing: improve `CircularBufferTest` (#6418)
* testing: improve CircularBufferTest
* style: redundant whitespace
---------
Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
---
.../buffers/CircularBufferTest.java | 152 +++++++++++++++++-
1 file changed, 148 insertions(+), 4 deletions(-)
diff --git a/src/test/java/com/thealgorithms/datastructures/buffers/CircularBufferTest.java b/src/test/java/com/thealgorithms/datastructures/buffers/CircularBufferTest.java
index b115fc187b1a..69af422e7175 100644
--- a/src/test/java/com/thealgorithms/datastructures/buffers/CircularBufferTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/buffers/CircularBufferTest.java
@@ -2,7 +2,6 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
@@ -68,11 +67,11 @@ void testFullBuffer() {
@Test
void testIllegalArguments() {
- assertThrows(IllegalArgumentException.class, () -> new CircularBuffer<>(0));
- assertThrows(IllegalArgumentException.class, () -> new CircularBuffer<>(-1));
+ org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> new CircularBuffer<>(0));
+ org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> new CircularBuffer<>(-1));
CircularBuffer buffer = new CircularBuffer<>(1);
- assertThrows(IllegalArgumentException.class, () -> buffer.put(null));
+ org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> buffer.put(null));
}
@Test
@@ -85,4 +84,149 @@ void testLargeBuffer() {
buffer.put(1000); // This should overwrite 0
assertEquals(1, buffer.get());
}
+
+ @Test
+ void testPutAfterGet() {
+ CircularBuffer buffer = new CircularBuffer<>(2);
+ buffer.put(10);
+ buffer.put(20);
+ assertEquals(10, buffer.get());
+ buffer.put(30);
+ assertEquals(20, buffer.get());
+ assertEquals(30, buffer.get());
+ assertNull(buffer.get());
+ }
+
+ @Test
+ void testMultipleWrapArounds() {
+ CircularBuffer buffer = new CircularBuffer<>(3);
+ for (int i = 1; i <= 6; i++) {
+ buffer.put(i);
+ buffer.get(); // add and immediately remove
+ }
+ assertTrue(buffer.isEmpty());
+ assertNull(buffer.get());
+ }
+
+ @Test
+ void testOverwriteMultipleTimes() {
+ CircularBuffer buffer = new CircularBuffer<>(2);
+ buffer.put("X");
+ buffer.put("Y");
+ buffer.put("Z"); // overwrites "X"
+ buffer.put("W"); // overwrites "Y"
+ assertEquals("Z", buffer.get());
+ assertEquals("W", buffer.get());
+ assertNull(buffer.get());
+ }
+
+ @Test
+ void testIsEmptyAndIsFullTransitions() {
+ CircularBuffer buffer = new CircularBuffer<>(2);
+ assertTrue(buffer.isEmpty());
+ org.junit.jupiter.api.Assertions.assertFalse(buffer.isFull());
+
+ buffer.put(1);
+ org.junit.jupiter.api.Assertions.assertFalse(buffer.isEmpty());
+ org.junit.jupiter.api.Assertions.assertFalse(buffer.isFull());
+
+ buffer.put(2);
+ assertTrue(buffer.isFull());
+
+ buffer.get();
+ org.junit.jupiter.api.Assertions.assertFalse(buffer.isFull());
+
+ buffer.get();
+ assertTrue(buffer.isEmpty());
+ }
+
+ @Test
+ void testInterleavedPutAndGet() {
+ CircularBuffer buffer = new CircularBuffer<>(3);
+ buffer.put("A");
+ buffer.put("B");
+ assertEquals("A", buffer.get());
+ buffer.put("C");
+ assertEquals("B", buffer.get());
+ assertEquals("C", buffer.get());
+ assertNull(buffer.get());
+ }
+
+ @Test
+ void testRepeatedNullInsertionThrows() {
+ CircularBuffer