diff --git a/DIRECTORY.md b/DIRECTORY.md index 042efa72addc..de07c7d29337 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -445,6 +445,7 @@ - 📄 [ConvolutionFFT](src/main/java/com/thealgorithms/maths/ConvolutionFFT.java) - 📄 [CrossCorrelation](src/main/java/com/thealgorithms/maths/CrossCorrelation.java) - 📄 [DeterminantOfMatrix](src/main/java/com/thealgorithms/maths/DeterminantOfMatrix.java) + - [DiscreteLogarithmBSGS](src/main/java/com/thealgorithms/maths/DiscreteLogarithmBSGS.java) - 📄 [DigitalRoot](src/main/java/com/thealgorithms/maths/DigitalRoot.java) - 📄 [DistanceFormula](src/main/java/com/thealgorithms/maths/DistanceFormula.java) - 📄 [DudeneyNumber](src/main/java/com/thealgorithms/maths/DudeneyNumber.java) diff --git a/src/main/java/com/thealgorithms/maths/DiscreteLogarithmBSGS.java b/src/main/java/com/thealgorithms/maths/DiscreteLogarithmBSGS.java new file mode 100644 index 000000000000..8084c5a42052 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/DiscreteLogarithmBSGS.java @@ -0,0 +1,74 @@ +package com.thealgorithms.maths; + +import java.util.HashMap; +import java.util.Map; + +/** + * Baby-Step Giant-Step algorithm for the Discrete Logarithm Problem. + * + *

+ * Solves for x in: a^x ≡ b (mod m), + * where a, b, m are given, and m is prime (or a has order in modulo m). + * + *

+ * Time complexity: O(√m) + * Space complexity: O(√m) + * + * @see Baby-step giant-step algorithm + */ +public final class DiscreteLogarithmBSGS { + + private DiscreteLogarithmBSGS() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * Computes x such that (a^x) % m == b. + * Returns -1 if no solution exists. + */ + public static long discreteLog(long a, long b, long m) { + a %= m; + b %= m; + + if (b == 1) { + return 0; + } + + long n = (long) Math.ceil(Math.sqrt(m)); + + Map babySteps = new HashMap<>(); + + long value = 1; + for (long i = 0; i < n; i++) { + babySteps.put(value, i); + value = value * a % m; // PMD fix + } + + long factor = modPow(a, m - n - 1, m); + + long gamma = b; + for (long j = 0; j <= n; j++) { + if (babySteps.containsKey(gamma)) { + return j * n + babySteps.get(gamma); + } + gamma = gamma * factor % m; // PMD fix + } + + return -1; // no solution + } + + /** Fast modular exponentiation */ + public static long modPow(long base, long exp, long mod) { + long result = 1; + base %= mod; + + while (exp > 0) { + if ((exp & 1) == 1) { + result = result * base % mod; // PMD fix + } + base = base * base % mod; // PMD fix + exp >>= 1; + } + return result; + } +} diff --git a/src/test/java/com/thealgorithms/maths/DiscreteLogarithmBSGSTest.java b/src/test/java/com/thealgorithms/maths/DiscreteLogarithmBSGSTest.java new file mode 100644 index 000000000000..3841e6e15869 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/DiscreteLogarithmBSGSTest.java @@ -0,0 +1,41 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class DiscreteLogarithmBSGSTest { + + @Test + public void testSmallValues() { + assertEquals(3, DiscreteLogarithmBSGS.discreteLog(2, 8, 13)); + assertEquals(4, DiscreteLogarithmBSGS.discreteLog(3, 81, 1000000007)); + } + + @Test + public void testNoSolution() { + // choose a true NO-SOLUTION example: + // modulo 15, base 4 generates only {1,4} + assertEquals(-1, DiscreteLogarithmBSGS.discreteLog(4, 2, 15)); + } + + @Test + public void testLargeMod() { + // use a valid solvable case + long x = DiscreteLogarithmBSGS.discreteLog(5, 5, 1000003); + assertEquals(1, x); + } + + @Test + public void testImmediateReturnCase() { + // because a^0 ≡ 1 (mod m) + assertEquals(0, DiscreteLogarithmBSGS.discreteLog(7, 1, 13)); + } + + @Test + public void testModPowBranchCoverage() { + assertEquals(1, DiscreteLogarithmBSGS.modPow(10, 0, 17)); // exp=0 branch + assertEquals(10 % 17, DiscreteLogarithmBSGS.modPow(10, 1, 17)); // odd exp branch + assertEquals(100 % 17, DiscreteLogarithmBSGS.modPow(10, 2, 17)); // even exp branch + } +}