CMSI 281
Homework #2
Partial Answers
  1. An interface for polynomials:
    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();
    }
    
  2. A class for polynomials implemented with bounded arrays:
    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();
        }
    }
    
  3. A UML class diagram for polynomials:

    To be done in class.

  4. Unit test for array polynomials:
    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);
        }
    }
    
  5. A rational numbers class:
    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();
            }
        }
    }
    
  6. A UML class diagram for rationals:

    To be done in class.

  7. Unit tests for rationals:
    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));
        }
    }