diff --git a/src/main/java/com/thealgorithms/maths/LinearDiophantineEquationsSolver.java b/src/main/java/com/thealgorithms/maths/LinearDiophantineEquationsSolver.java index a50cfb218283..a95e7053e210 100644 --- a/src/main/java/com/thealgorithms/maths/LinearDiophantineEquationsSolver.java +++ b/src/main/java/com/thealgorithms/maths/LinearDiophantineEquationsSolver.java @@ -2,20 +2,77 @@ import java.util.Objects; +/** + * A solver for linear Diophantine equations of the form ax + by = c. + *

+ * A linear Diophantine equation is an equation in which only integer solutions + * are allowed. + * This solver uses the Extended Euclidean Algorithm to find integer solutions + * (x, y) + * for equations of the form ax + by = c, where a, b, and c are integers. + *

+ *

+ * The equation has solutions if and only if gcd(a, b) divides c. + * If solutions exist, this solver finds one particular solution. + *

+ * + * @see Diophantine + * Equation + * @see Extended + * Euclidean Algorithm + */ public final class LinearDiophantineEquationsSolver { private LinearDiophantineEquationsSolver() { } + /** + * Demonstrates the solver with a sample equation: 3x + 4y = 7. + * + * @param args command line arguments (not used) + */ public static void main(String[] args) { // 3x + 4y = 7 final var toSolve = new Equation(3, 4, 7); System.out.println(findAnySolution(toSolve)); } + /** + * Finds any integer solution to the linear Diophantine equation ax + by = c. + *

+ * The method returns one of three types of solutions: + *

+ *

+ * + * @param equation the linear Diophantine equation to solve + * @return a Solution object containing the result + * @throws NullPointerException if equation is null + */ public static Solution findAnySolution(final Equation equation) { if (equation.a() == 0 && equation.b() == 0 && equation.c() == 0) { return Solution.INFINITE_SOLUTIONS; } + if (equation.a() == 0 && equation.b() == 0) { + return Solution.NO_SOLUTION; + } + if (equation.a() == 0) { + if (equation.c() % equation.b() == 0) { + return new Solution(0, equation.c() / equation.b()); + } else { + return Solution.NO_SOLUTION; + } + } + if (equation.b() == 0) { + if (equation.c() % equation.a() == 0) { + return new Solution(equation.c() / equation.a(), 0); + } else { + return Solution.NO_SOLUTION; + } + } final var stub = new GcdSolutionWrapper(0, new Solution(0, 0)); final var gcdSolution = gcd(equation.a(), equation.b(), stub); if (equation.c() % gcdSolution.getGcd() != 0) { @@ -29,43 +86,100 @@ public static Solution findAnySolution(final Equation equation) { return toReturn; } + /** + * Computes the GCD of two integers using the Extended Euclidean Algorithm. + *

+ * This method also finds coefficients x and y such that ax + by = gcd(a, b). + * The coefficients are stored in the 'previous' wrapper object. + *

+ * + * @param a the first integer + * @param b the second integer + * @param previous a wrapper to store the solution coefficients + * @return a GcdSolutionWrapper containing the GCD and coefficients + */ private static GcdSolutionWrapper gcd(final int a, final int b, final GcdSolutionWrapper previous) { if (b == 0) { return new GcdSolutionWrapper(a, new Solution(1, 0)); } // stub wrapper becomes the `previous` of the next recursive call final var stubWrapper = new GcdSolutionWrapper(0, new Solution(0, 0)); - final var next = /* recursive call */ gcd(b, a % b, stubWrapper); + final var next = gcd(b, a % b, stubWrapper); previous.getSolution().setX(next.getSolution().getY()); previous.getSolution().setY(next.getSolution().getX() - (a / b) * (next.getSolution().getY())); previous.setGcd(next.getGcd()); return new GcdSolutionWrapper(next.getGcd(), previous.getSolution()); } + /** + * Represents a solution (x, y) to a linear Diophantine equation. + *

+ * Special instances: + *

+ *

+ */ public static final class Solution { + /** + * Singleton instance representing the case where no solution exists. + */ public static final Solution NO_SOLUTION = new Solution(Integer.MAX_VALUE, Integer.MAX_VALUE); + + /** + * Singleton instance representing the case where infinite solutions exist. + */ public static final Solution INFINITE_SOLUTIONS = new Solution(Integer.MIN_VALUE, Integer.MIN_VALUE); + private int x; private int y; + /** + * Constructs a solution with the given x and y values. + * + * @param x the x coordinate of the solution + * @param y the y coordinate of the solution + */ public Solution(int x, int y) { this.x = x; this.y = y; } + /** + * Gets the x value of this solution. + * + * @return the x value + */ public int getX() { return x; } + /** + * Gets the y value of this solution. + * + * @return the y value + */ public int getY() { return y; } + /** + * Sets the x value of this solution. + * + * @param x the new x value + */ public void setX(int x) { this.x = x; } + /** + * Sets the y value of this solution. + * + * @param y the new y value + */ public void setY(int y) { this.y = y; } @@ -95,14 +209,35 @@ public String toString() { } } + /** + * Represents a linear Diophantine equation of the form ax + by = c. + * + * @param a the coefficient of x + * @param b the coefficient of y + * @param c the constant term + */ public record Equation(int a, int b, int c) { } + /** + * A wrapper class that holds both the GCD and the solution coefficients + * from the Extended Euclidean Algorithm. + *

+ * This class is used internally to pass results between recursive calls + * of the GCD computation. + *

+ */ public static final class GcdSolutionWrapper { private int gcd; private Solution solution; + /** + * Constructs a GcdSolutionWrapper with the given GCD and solution. + * + * @param gcd the greatest common divisor + * @param solution the solution coefficients + */ public GcdSolutionWrapper(int gcd, Solution solution) { this.gcd = gcd; this.solution = solution; @@ -120,18 +255,38 @@ public boolean equals(Object obj) { return (this.gcd == that.gcd && Objects.equals(this.solution, that.solution)); } + /** + * Gets the GCD value. + * + * @return the GCD + */ public int getGcd() { return gcd; } + /** + * Sets the GCD value. + * + * @param gcd the new GCD value + */ public void setGcd(int gcd) { this.gcd = gcd; } + /** + * Gets the solution coefficients. + * + * @return the solution + */ public Solution getSolution() { return solution; } + /** + * Sets the solution coefficients. + * + * @param solution the new solution + */ public void setSolution(Solution solution) { this.solution = solution; } diff --git a/src/test/java/com/thealgorithms/maths/LinearDiophantineEquationsSolverTest.java b/src/test/java/com/thealgorithms/maths/LinearDiophantineEquationsSolverTest.java new file mode 100644 index 000000000000..c4205985dbfd --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/LinearDiophantineEquationsSolverTest.java @@ -0,0 +1,330 @@ +package com.thealgorithms.maths; + +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 static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Test class for LinearDiophantineEquationsSolver. + * Tests various cases including: + * - Equations with solutions + * - Equations with no solutions + * - Special cases (zero coefficients, infinite solutions) + * - Edge cases (negative coefficients, large numbers) + */ +class LinearDiophantineEquationsSolverTest { + + /** + * Tests the example equation 3x + 4y = 7. + * Expected solution: x = -9, y = 8 (or other valid solutions). + */ + @Test + void testBasicEquation() { + final var equation = new LinearDiophantineEquationsSolver.Equation(3, 4, 7); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + // Verify that the solution satisfies the equation + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } + + /** + * Tests an equation with no solution: 2x + 4y = 5. + * Since gcd(2, 4) = 2 and 2 does not divide 5, no solution exists. + */ + @Test + void testNoSolution() { + final var equation = new LinearDiophantineEquationsSolver.Equation(2, 4, 5); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertEquals(LinearDiophantineEquationsSolver.Solution.NO_SOLUTION, solution); + } + + /** + * Tests the trivial equation 0x + 0y = 0. + * This has infinite solutions. + */ + @Test + void testInfiniteSolutions() { + final var equation = new LinearDiophantineEquationsSolver.Equation(0, 0, 0); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertEquals(LinearDiophantineEquationsSolver.Solution.INFINITE_SOLUTIONS, solution); + } + + /** + * Tests an equation where a = 0: 0x + 5y = 10. + * Expected solution: x = 0, y = 2. + */ + @Test + void testZeroCoefficient() { + final var equation = new LinearDiophantineEquationsSolver.Equation(0, 5, 10); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } + + /** + * Tests an equation where b = 0: 3x + 0y = 9. + * Expected solution: x = 3, y can be anything (solver will return y = 0). + */ + @Test + void testZeroCoefficientB() { + final var equation = new LinearDiophantineEquationsSolver.Equation(3, 0, 9); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } + + /** + * Tests an equation with negative coefficients: -3x + 4y = 7. + */ + @Test + void testNegativeCoefficients() { + final var equation = new LinearDiophantineEquationsSolver.Equation(-3, 4, 7); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } + + /** + * Tests an equation with negative result: 3x + 4y = -7. + */ + @Test + void testNegativeResult() { + final var equation = new LinearDiophantineEquationsSolver.Equation(3, 4, -7); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } + + /** + * Tests an equation with coprime coefficients: 7x + 11y = 1. + * Since gcd(7, 11) = 1, a solution exists. + */ + @Test + void testCoprimeCoefficients() { + final var equation = new LinearDiophantineEquationsSolver.Equation(7, 11, 1); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } + + /** + * Tests an equation with larger coefficients: 12x + 18y = 30. + * Since gcd(12, 18) = 6 and 6 divides 30, a solution exists. + */ + @Test + void testLargerCoefficients() { + final var equation = new LinearDiophantineEquationsSolver.Equation(12, 18, 30); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } + + /** + * Tests an equation that has no solution due to GCD: 6x + 9y = 5. + * Since gcd(6, 9) = 3 and 3 does not divide 5, no solution exists. + */ + @Test + void testNoSolutionGcdCheck() { + final var equation = new LinearDiophantineEquationsSolver.Equation(6, 9, 5); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertEquals(LinearDiophantineEquationsSolver.Solution.NO_SOLUTION, solution); + } + + /** + * Tests the equation x + y = 1. + * Simple case where gcd(1, 1) = 1. + */ + @Test + void testSimpleCase() { + final var equation = new LinearDiophantineEquationsSolver.Equation(1, 1, 1); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } + + /** + * Tests Solution equality. + */ + @Test + void testSolutionEquality() { + final var solution1 = new LinearDiophantineEquationsSolver.Solution(3, 5); + final var solution2 = new LinearDiophantineEquationsSolver.Solution(3, 5); + final var solution3 = new LinearDiophantineEquationsSolver.Solution(3, 6); + + assertEquals(solution1, solution2); + assertNotEquals(solution3, solution1); + assertEquals(solution1, solution1); + assertNotEquals(null, solution1); + assertNotEquals("string", solution1); + } + + /** + * Tests Solution hashCode. + */ + @Test + void testSolutionHashCode() { + final var solution1 = new LinearDiophantineEquationsSolver.Solution(3, 5); + final var solution2 = new LinearDiophantineEquationsSolver.Solution(3, 5); + + assertEquals(solution1.hashCode(), solution2.hashCode()); + } + + /** + * Tests Solution toString. + */ + @Test + void testSolutionToString() { + final var solution = new LinearDiophantineEquationsSolver.Solution(3, 5); + final var str = solution.toString(); + + assertTrue(str.contains("3")); + assertTrue(str.contains("5")); + assertTrue(str.contains("Solution")); + } + + /** + * Tests GcdSolutionWrapper equality. + */ + @Test + void testGcdSolutionWrapperEquality() { + final var solution = new LinearDiophantineEquationsSolver.Solution(1, 2); + final var wrapper1 = new LinearDiophantineEquationsSolver.GcdSolutionWrapper(5, solution); + final var wrapper2 = new LinearDiophantineEquationsSolver.GcdSolutionWrapper(5, solution); + final var wrapper3 = new LinearDiophantineEquationsSolver.GcdSolutionWrapper(6, solution); + + assertEquals(wrapper1, wrapper2); + assertNotEquals(wrapper3, wrapper1); + assertEquals(wrapper1, wrapper1); + assertNotEquals(null, wrapper1); + assertNotEquals("string", wrapper1); + } + + /** + * Tests GcdSolutionWrapper hashCode. + */ + @Test + void testGcdSolutionWrapperHashCode() { + final var solution = new LinearDiophantineEquationsSolver.Solution(1, 2); + final var wrapper1 = new LinearDiophantineEquationsSolver.GcdSolutionWrapper(5, solution); + final var wrapper2 = new LinearDiophantineEquationsSolver.GcdSolutionWrapper(5, solution); + + assertEquals(wrapper1.hashCode(), wrapper2.hashCode()); + } + + /** + * Tests GcdSolutionWrapper toString. + */ + @Test + void testGcdSolutionWrapperToString() { + final var solution = new LinearDiophantineEquationsSolver.Solution(1, 2); + final var wrapper = new LinearDiophantineEquationsSolver.GcdSolutionWrapper(5, solution); + final var str = wrapper.toString(); + + assertTrue(str.contains("5")); + assertTrue(str.contains("GcdSolutionWrapper")); + } + + /** + * Tests Equation record functionality. + */ + @Test + void testEquationRecord() { + final var equation = new LinearDiophantineEquationsSolver.Equation(3, 4, 7); + + assertEquals(3, equation.a()); + assertEquals(4, equation.b()); + assertEquals(7, equation.c()); + } + + /** + * Tests an equation with c = 0: 3x + 4y = 0. + * Expected solution: x = 0, y = 0. + */ + @Test + void testZeroResult() { + final var equation = new LinearDiophantineEquationsSolver.Equation(3, 4, 0); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } + + /** + * Tests Solution setters. + */ + @Test + void testSolutionSetters() { + final var solution = new LinearDiophantineEquationsSolver.Solution(1, 2); + + solution.setX(10); + solution.setY(20); + + assertEquals(10, solution.getX()); + assertEquals(20, solution.getY()); + } + + /** + * Tests GcdSolutionWrapper setters. + */ + @Test + void testGcdSolutionWrapperSetters() { + final var solution = new LinearDiophantineEquationsSolver.Solution(1, 2); + final var wrapper = new LinearDiophantineEquationsSolver.GcdSolutionWrapper(5, solution); + + final var newSolution = new LinearDiophantineEquationsSolver.Solution(3, 4); + wrapper.setGcd(10); + wrapper.setSolution(newSolution); + + assertEquals(10, wrapper.getGcd()); + assertEquals(newSolution, wrapper.getSolution()); + } + + /** + * Tests an equation with both coefficients negative: -3x - 4y = -7. + */ + @Test + void testBothCoefficientsNegative() { + final var equation = new LinearDiophantineEquationsSolver.Equation(-3, -4, -7); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } + + /** + * Tests an equation with large prime coefficients: 97x + 101y = 198. + */ + @Test + void testLargePrimeCoefficients() { + final var equation = new LinearDiophantineEquationsSolver.Equation(97, 101, 198); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } +}