Skip to content

refactor: Enhance docs, add more tests in AffineConverter #5915

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Oct 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,23 +1,64 @@
package com.thealgorithms.conversions;

/**
* A utility class to perform affine transformations of the form:
* y = slope * x + intercept.
*
* This class supports inversion and composition of affine transformations.
* It is immutable, meaning each instance represents a fixed transformation.
*/
public final class AffineConverter {
private final double slope;
private final double intercept;

/**
* Constructs an AffineConverter with the given slope and intercept.
*
* @param inSlope The slope of the affine transformation.
* @param inIntercept The intercept (constant term) of the affine transformation.
* @throws IllegalArgumentException if either parameter is NaN.
*/
public AffineConverter(final double inSlope, final double inIntercept) {
if (Double.isNaN(inSlope) || Double.isNaN(inIntercept)) {
throw new IllegalArgumentException("Slope and intercept must be valid numbers.");
}
slope = inSlope;
intercept = inIntercept;
}

/**
* Converts the given input value using the affine transformation:
* result = slope * inValue + intercept.
*
* @param inValue The input value to convert.
* @return The transformed value.
*/
public double convert(final double inValue) {
return slope * inValue + intercept;
}

/**
* Returns a new AffineConverter representing the inverse of the current transformation.
* The inverse of y = slope * x + intercept is x = (y - intercept) / slope.
*
* @return A new AffineConverter representing the inverse transformation.
* @throws AssertionError if the slope is zero, as the inverse would be undefined.
*/
public AffineConverter invert() {
assert slope != 0.0;
assert slope != 0.0 : "Slope cannot be zero for inversion.";
return new AffineConverter(1.0 / slope, -intercept / slope);
}

/**
* Composes this affine transformation with another, returning a new AffineConverter.
* If this transformation is f(x) and the other is g(x), the result is f(g(x)).
*
* @param other Another AffineConverter to compose with.
* @return A new AffineConverter representing the composition of the two transformations.
*/
public AffineConverter compose(final AffineConverter other) {
return new AffineConverter(slope * other.slope, slope * other.intercept + intercept);
double newSlope = slope * other.slope;
double newIntercept = slope * other.intercept + intercept;
return new AffineConverter(newSlope, newIntercept);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,39 @@ void setUp() {
}

@Test
void testConstructor() {
void testConstructorWithValidValues() {
assertEquals(3.0, converter.convert(0.0), "Expected value when input is 0.0");
assertEquals(5.0, converter.convert(1.0), "Expected value when input is 1.0");
assertEquals(7.0, converter.convert(2.0), "Expected value when input is 2.0");
}

@Test
void testConvert() {
assertEquals(3.0, converter.convert(0.0), "Conversion at 0.0 should equal the intercept");
assertEquals(7.0, converter.convert(2.0), "2.0 should convert to 7.0");
assertEquals(11.0, converter.convert(4.0), "4.0 should convert to 11.0");
void testConstructorWithInvalidValues() {
assertThrows(IllegalArgumentException.class, () -> new AffineConverter(Double.NaN, 3.0), "Constructor should throw IllegalArgumentException for NaN slope");
}

@Test
void testConvertWithNegativeValues() {
assertEquals(-1.0, converter.convert(-2.0), "Negative input should convert correctly");
assertEquals(-3.0, new AffineConverter(-1.0, -1.0).convert(2.0), "Slope and intercept can be negative");
}

@Test
void testConvertWithFloatingPointPrecision() {
double result = new AffineConverter(1.3333, 0.6667).convert(3.0);
assertEquals(4.6666, result, 1e-4, "Conversion should maintain floating-point precision");
}

@Test
void testInvert() {
AffineConverter inverted = converter.invert();
assertEquals(0.0, inverted.convert(3.0), "Inverted converter should return 0.0 for input 3.0");
assertEquals(1.0, inverted.convert(5.0), "Inverted converter should return 1.0 for input 5.0");
assertEquals(2.0, inverted.convert(7.0), "Inverted converter should return 2.0 for input 7.0");
assertEquals(0.0, inverted.convert(3.0), "Inverted should return 0.0 for input 3.0");
assertEquals(1.0, inverted.convert(5.0), "Inverted should return 1.0 for input 5.0");
}

@Test
void testInvertWithZeroSlope() {
AffineConverter zeroSlopeConverter = new AffineConverter(0.0, 3.0);
assertThrows(AssertionError.class, zeroSlopeConverter::invert, "Invert should throw assertion error when slope is zero");
assertThrows(AssertionError.class, zeroSlopeConverter::invert, "Invert should throw AssertionError when slope is zero");
}

@Test
Expand All @@ -50,6 +58,30 @@ void testCompose() {

assertEquals(7.0, composed.convert(0.0), "Expected composed conversion at 0.0");
assertEquals(9.0, composed.convert(1.0), "Expected composed conversion at 1.0");
assertEquals(11.0, composed.convert(2.0), "Expected composed conversion at 2.0");
}

@Test
void testMultipleCompositions() {
AffineConverter c1 = new AffineConverter(2.0, 1.0);
AffineConverter c2 = new AffineConverter(3.0, -2.0);
AffineConverter c3 = c1.compose(c2); // (2x + 1) ∘ (3x - 2) => 6x - 1

assertEquals(-3.0, c3.convert(0.0), "Composed transformation should return -3.0 at 0.0");
assertEquals(3.0, c3.convert(1.0), "Composed transformation should return 3.0 at 1.0");
}

@Test
void testIdentityComposition() {
AffineConverter identity = new AffineConverter(1.0, 0.0);
AffineConverter composed = converter.compose(identity);

assertEquals(3.0, composed.convert(0.0), "Identity composition should not change the transformation");
assertEquals(7.0, composed.convert(2.0), "Identity composition should behave like the original");
}

@Test
void testLargeInputs() {
double largeValue = 1e6;
assertEquals(2.0 * largeValue + 3.0, converter.convert(largeValue), "Should handle large input values without overflow");
}
}