package edu.lmu.cs.math;
/**
* A primitive interface for polynomials <strong>with non-negative
* exponents only</strong>. A polynomial is an expression
* of the form a0*x^0 + a1*x^1 + ... + ak*x^k where a1..ak are real
* numbers called coefficients, and 0..k are called the exponents.
*/
public interface Polynomial {
/**
* Returns the coefficent for the term with the given exponent.
*/
double getCoefficient(int exponent);
/**
* Sets the coefficient of the term with the given exponent to a new
* value, overwriting the previous value if one existed.
*/
void setCoefficient(int exponent, double coefficient);
/**
* Returns the largest exponent in the polynomial.
*/
int getDegree();
/**
* Returns the value of this polynomial at x.
*/
double evaluate(double x);
/**
* Returns the sum of this polynomial and p.
*/
Polynomial plus(Polynomial p);
/**
* Returns the difference of this polynomial and p.
*/
Polynomial minus(Polynomial p);
/**
* Returns the derivative of this polynomial.
*/
Polynomial derivative();
}
package edu.lmu.cs.math;
import java.util.Arrays;
/**
* An implementation of the Polynomial interface using an array
* indexed by exponent, of coefficients.
*/
public class ArrayPolynomial implements Polynomial {
// Array of coefficients, indexed by exponent. This class
// maintains an invariant that this array is never null nor
// is of size zero.
private double[] coefficients;
/**
* Creates the zero polynomial.
*/
public ArrayPolynomial() {
this(1);
}
/**
* Creates the zero polynomial but with an internal array of
* the requested size. If the requested size is less than 1,
* it is bumped up to 1.
*/
private ArrayPolynomial(int capacity) {
this.coefficients = new double[Math.max(capacity, 1)];
Arrays.fill(coefficients, 0.0);
}
/**
* Constructs a polynomial from an array of coefficients.
* If the array that was passed in is null or empty, the
* constructed polynomial will be the zero polynomial.
*/
public ArrayPolynomial(double[] coefficients) {
if (coefficients == null || coefficients.length < 1) {
this.coefficients = new double[]{0.0};
} else {
this.coefficients = new double[coefficients.length];
System.arraycopy(coefficients, 0, this.coefficients, 0,
coefficients.length);
}
}
/**
* Returns the coefficent for the term with the given exponent.
*/
public double getCoefficient(int exponent) {
if (exponent < 0) {
throw new IllegalArgumentException(
"Negative exponents are not allowed");
}
// Return the stored coefficient if the exponent is in
// range of the internal array; return zero otherwise
// since doing so makes perfect mathematical sense.
return exponent >= coefficients.length ? 0 : coefficients[exponent];
}
/**
* Sets the coefficient of the term with the given exponent to a new
* value, overwriting the previous value if one existed.
*/
public void setCoefficient(int exponent, double coefficient) {
if (exponent < 0) {
throw new IllegalArgumentException(
"Negative exponents are not allowed");
}
// Expand the array if necessary.
if (exponent >= coefficients.length) {
double[] newArray = new double[exponent + 1];
Arrays.fill(newArray, 0.0);
System.arraycopy(
coefficients, 0,
newArray, 0,
coefficients.length);
coefficients = newArray;
}
// Update the coefficient
coefficients[exponent] = coefficient;
}
/**
* Returns the largest exponent in the polynomial.
*/
public int getDegree() {
// Returns the largest array index containing a non-zero value.
for (int i = coefficients.length - 1; i > 0; i++) {
if (coefficients[i] != 0.0) {
return i;
}
}
// If there are no non-zero coefficients, the polynomial
// is zero, which has degree 0.
return 0;
}
/**
* Returns the value of this polynomial at x.
*/
public double evaluate(double x) {
double sum = 0.0;
for (int i = 0; i < coefficients.length; i++) {
sum += coefficients[i] * Math.pow(x, i);
}
return sum;
}
/**
* Returns the sum of this polynomial and p.
*/
public Polynomial plus(Polynomial p) {
return this.plusScaled(1, p);
}
/**
* Returns the difference of this polynomial and p.
*/
public Polynomial minus(Polynomial p) {
return this.plusScaled(-1, p);
}
// Cool helper: returns this+k*p, for constant k and polynomial p.
public Polynomial plusScaled(double k, Polynomial p) {
int newDegree = Math.max(getDegree(), p.getDegree());
Polynomial result = new ArrayPolynomial(newDegree);
for (int i = newDegree; i >= 0; i--) {
result.setCoefficient(i, getCoefficient(i) + k * p.getCoefficient(i));
}
return result;
}
/**
* Returns the derivative of this polynomial.
*/
public Polynomial derivative() {
Polynomial result = new ArrayPolynomial(coefficients.length - 1);
for (int i = 0; i < coefficients.length - 1; i++) {
result.setCoefficient(i, (i + 1) * getCoefficient(i + 1));
}
return result;
}
/**
* Returns a representation of the polynomial, something like
* "4.3x^3-3.0x+1.11". We're fairly intelligent here, since we
* suppress leading '+' signs, exponents of 1, coefficients of 1,
* and we completely skip any term with a zero coefficient (unless
* of course all terms have zero coefficients, in which case we
* return simply "0").
*/
public String toString() {
StringBuilder builder = new StringBuilder();
for (int e = coefficients.length-1; e >= 0; e--) {
double c = coefficients[e];
if (c == 0) continue;
if (c > 0 && builder.length() > 0) builder.append("+");
if (c != 1.0) builder.append(c);
if (e == 0) continue;
builder.append("x");
if (e != 1) builder.append("^" + e);
}
// If nothing was accumulated into the buffer yet, all
// coefficients are 0
return (builder.length() == 0) ? "0" : builder.toString();
}
}
To be done in class.
package edu.lmu.cs.math;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
/**
* A trivial tester for the ArrayPolynomial class.
*/
public class ArrayPolynomialTest {
@Test
public void testZeroUponConstruction() {
Polynomial p = new ArrayPolynomial();
assertEquals(p.evaluate(10), 0.0, 0.000001);
assertEquals(p.getDegree(), 0);
assertEquals(p.toString(), "0");
}
@Test
public void testSimplePolynomial() {
Polynomial p = new ArrayPolynomial();
p.setCoefficient(2, 3.0); // 3x^2
p.setCoefficient(1, 2.0); // 3x^2+2x
assertEquals(p.getCoefficient(7), 0, 0.0000001);
assertEquals(p.getDegree(), 2);
assertEquals(p.toString(), "3.0x^2+2.0x");
}
@Test
public void testDerivative() {
Polynomial p = new ArrayPolynomial();
p.setCoefficient(2, 3.0); // 3x^2
p.setCoefficient(1, 2.0); // 3x^2+2x
Polynomial q = p.derivative();
assertEquals(q.getCoefficient(1), 6, 0.0000001);
assertEquals(q.getCoefficient(0), 2, 0.0000001);
assertEquals(q.getDegree(), 1);
assertEquals(p.evaluate(5), 85.0, 0.000001);
assertEquals(q.toString(), "6.0x+2.0");
q = q.derivative(); // 6
assertEquals(q.toString(), "6.0");
q = q.derivative(); // 0
assertEquals(q.toString(), "0");
q = q.derivative(); // 0
assertEquals(q.toString(), "0");
}
@Test
public void testAddition() {
Polynomial p = new ArrayPolynomial();
p.setCoefficient(2, 3.0); // 3x^2
p.setCoefficient(1, 2.0); // 3x^2+2x
Polynomial q = p.derivative(); // 6x + 2
q = q.plus(p);
q.setCoefficient(7, 1);
assertEquals(q.getCoefficient(1), 8, 0.0000001);
assertEquals(q.getCoefficient(0), 2, 0.0000001);
assertEquals(q.getDegree(), 7);
assertEquals(q.evaluate(2), 158, 0.0000001);
assertEquals(q.toString(), "x^7+3.0x^2+8.0x+2.0");
}
@Test
public void testZeroCoefficients() {
Polynomial q = new ArrayPolynomial();
q.setCoefficient(5, 1);
q.setCoefficient(2, 1);
assertEquals(q.toString(), "x^5+x^2");
}
@Test
public void testComplexConstructorAndNegativeCoefficients() {
Polynomial p = new ArrayPolynomial(new double[]{3.5, -4, 0, 0, -2});
assertEquals(p.toString(), "-2.0x^4-4.0x+3.5");
}
@Test(expected=IllegalArgumentException.class)
public void testNegativeExponentOnSet() {
Polynomial q = new ArrayPolynomial();
q.setCoefficient(-1, 5);
}
@Test(expected=IllegalArgumentException.class)
public void testNegativeExponentOnGet() {
Polynomial q = new ArrayPolynomial();
q.getCoefficient(-5);
}
}
package edu.lmu.cs.math;
import java.math.BigInteger;
/**
* A class for rational numbers, using BigIntegers for the components.
* BigIntegers may be slow, but in using them we don't have to deal
* with the messiness of overflow and of the nasty "most negative
* integer" deal.
*/
public class Rational {
private BigInteger num;
private BigInteger den;
/**
* Constructs a rational number equal to the given value.
*/
public Rational(BigInteger value) {
this(value, BigInteger.ONE);
}
/**
* Constructs a rational number whose value is <code>num/den</code>.
* @throws IllegalArgumentException if <code>den = 0</code>.
*/
public Rational(long num, long den) {
this(BigInteger.valueOf(num), BigInteger.valueOf(den));
}
/**
* Constructs a rational number whose value is <code>num/den</code>.
* @throws IllegalArgumentException if <code>den = 0</code>
* or any of the components are null.
*/
public Rational(BigInteger num, BigInteger den) {
if (num == null) {
throw new IllegalArgumentException("Numerator can't be null");
}
if (den == null) {
throw new IllegalArgumentException("Denominator can't be null");
}
if (den.equals(BigInteger.ZERO)) {
throw new IllegalArgumentException("Denominator can't be zero");
}
this.num = num;
this.den = den;
normalize();
}
/**
* Returns the numerator.
*/
public BigInteger getNum() {
return num;
}
/**
* Returns the denominator.
*/
public BigInteger getDen() {
return den;
}
/**
* Returns the sum of this Rational and r.
*/
public Rational plus(Rational r) {
return new Rational(num.multiply(r.den).add(r.num.multiply(den)),
den.multiply(r.den));
}
/**
* Returns the difference of this Rational and r.
*/
public Rational minus(Rational r) {
return new Rational(num.multiply(r.den).subtract(r.num.multiply(den)),
den.multiply(r.den));
}
/**
* Returns the product of this Rational and r.
*/
public Rational times(Rational r) {
return new Rational(num.multiply(r.num), den.multiply(r.den));
}
/**
* Returns the quotient of this Rational and r.
*/
public Rational dividedBy(Rational r) {
return new Rational(num.multiply(r.den), den.multiply(r.num));
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((den == null) ? 0 : den.hashCode());
result = PRIME * result + ((num == null) ? 0 : num.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
final Rational other = (Rational) obj;
if (den == null) {
if (other.den != null)
return false;
} else if (!den.equals(other.den))
return false;
if (num == null) {
if (other.num != null)
return false;
} else if (!num.equals(other.num))
return false;
return true;
}
@Override
public String toString() {
return "(" + num + "/" + den + ")";
}
/**
* Normalizes this number (puts in into a form that is unique
* for its value). It works by ensuring that num and den have no
* common factors, that the value zero is always represented by
* 0/1, and that the denominator is always positive.
*/
private void normalize() {
if (BigInteger.ZERO.equals(num)) {
den = BigInteger.ONE;
return;
}
BigInteger gcd = num.gcd(den);
num = num.divide(gcd);
den = den.divide(gcd);
if (den.compareTo(BigInteger.ZERO) < 0) {
num = num.negate();
den = den.negate();
}
}
}
To be done in class.
package edu.lmu.cs.math;
import static org.junit.Assert.assertEquals;
import java.math.BigInteger;
import org.junit.Test;
/**
* A trivial tester for rational numbers.
*/
public class RationalTest {
private Rational r1 = new Rational(BigInteger.ZERO);
private Rational r2 = new Rational(BigInteger.valueOf(5));
private Rational r3 = new Rational(6, 12);
private Rational r4 = new Rational(99, -44);
@Test
public void testGetters() {
assertEquals(r1.getNum(), BigInteger.valueOf(0));
assertEquals(r1.getDen(), BigInteger.valueOf(1));
assertEquals(r2.getNum(), BigInteger.valueOf(5));
assertEquals(r2.getDen(), BigInteger.valueOf(1));
assertEquals(r3.getNum(), BigInteger.valueOf(1));
assertEquals(r3.getDen(), BigInteger.valueOf(2));
assertEquals(r4.getNum(), BigInteger.valueOf(-9));
assertEquals(r4.getDen(), BigInteger.valueOf(4));
}
@Test
public void testEquals() {
assertEquals(r1, new Rational(0, 6));
assertEquals(r2, new Rational(500, 100));
assertEquals(r3, new Rational(1, 2));
assertEquals(r4, new Rational(18, -8));
}
@Test
public void testOperations() {
assertNumeratorAndDenominator(r1.plus(r3), 1, 2);
assertNumeratorAndDenominator(r1.minus(r3), -1, 2);
assertNumeratorAndDenominator(r1.times(r3), 0, 1);
assertNumeratorAndDenominator(r1.dividedBy(r3), 0, 1);
assertNumeratorAndDenominator(r2.plus(r3), 11, 2);
assertNumeratorAndDenominator(r2.minus(r3), 9, 2);
assertNumeratorAndDenominator(r2.times(r3), 5, 2);
assertNumeratorAndDenominator(r2.dividedBy(r3), 10, 1);
assertNumeratorAndDenominator(r3.plus(r4), -7, 4);
assertNumeratorAndDenominator(r3.minus(r4), 11, 4);
assertNumeratorAndDenominator(r3.times(r4), -9, 8);
assertNumeratorAndDenominator(r3.dividedBy(r4), -2, 9);
}
@Test(expected=IllegalArgumentException.class)
public void testZeroDenominator() {
new Rational(10, 0);
}
@Test(expected=IllegalArgumentException.class)
public void testNullDenominator() {
new Rational(BigInteger.ONE, null);
}
@Test(expected=IllegalArgumentException.class)
public void testNullNumerator() {
new Rational(null, BigInteger.ONE);
}
private void assertNumeratorAndDenominator(Rational r, long n, long d) {
assertEquals(r.getNum(), BigInteger.valueOf(n));
assertEquals(r.getDen(), BigInteger.valueOf(d));
}
}