Skip to main content
added 2717 characters in body
Source Link
mdfst13
  • 21.7k
  • 6
  • 33
  • 68
class Quotient implements Expression {

    private final Expression numerator;
    private final Expression denominator;

    public Quotient(Expression numerator, Expression denominator) {
         this.numerator = numerator;
         this.denominator = denominator;
    }

    public Expression getNumerator() {
        return numerator;
    }

    public Expression getDenominator() {
        return denominator;
    }

    @Override
    public Expression takeDerivative() {
        return new Quotient(
                new Difference(
                        new Product(numerator.takeDerivative(), denominator),
                        new Product(numerator, denominator.takeDerivative()),
                    ),
                new Product(denominator, denominator)
            );
    }

    @Override
    public Expression simplify() {
        Expression n = numerator.simplify();
        Expression d = denominator.simplify();

        if (n.equals(Constant.ZERO) || d.equals(Constant.ONE)) {
            return n;
        }

        if (n.equals(d)) {
            return Constant.ONE;
        }

        if (d instanceof Quotient) {
            if (n instanceof Quotient) {
                return new Quotient(
                        new Product(n.getNumerator(), d.getDenominator()),
                        new Product(d.getNumerator(), n.getDenominator())
                    ).simplify();
            } else {
                return new Quotient(
                        new Product(n, d.getDenominator()),
                        d.getNumerator()
                    ).simplify();
            }
        } else if (n instanceof Quotient) {
            return new Quotient(
                    n.getNumerator(),
                    new Product(d, n.getDenominator())
                ).simplify();
        }

        return new Quotient(n, d);
    }

    @Override
    public Operator getOperator() {
        return DIVISION;
    }

    @Override
    public boolean equals(Object object) {
        if (! object instanceof Expression) {
            return false;
        }

        if (object == this) {
            return true;
        }

        Expression t = simplify();
        Expression o = ((Expression) object).simplify();

        if (! t instanceof Quotient) {
            return t.equals(o);
        }

        if (! o instanceof Quotient) {
            return false;
        }

        Quotient qt = (Quotient) t;
        Quotient qo = (Quotient) o;

        return qt.getNumerator().equals(qo.getNumerator())
                && qt.getDenominator().equals(qo.getDenominator());
    }

}

If you make separate classes for each operation, you don't need the switch anymore. Each class provides its own method instead. This can work for both takeDerivative and for simplify.

I did not implement hashCode, but it might be advisable to do so. Otherwise, funky things could happen if you used this as a key in a HashMap, put it in a HashSet, or otherwise did something that relied on hashCode.

I may be missing simplification cases. For example, \$\frac{x^2 + x}{x}\$ would not simplify to \$x+1 \forall x \ne 0\$ with this method. These are the ones that came to mind with easy implementations.

class Quotient implements Expression {

    private final Expression numerator;
    private final Expression denominator;

    public Quotient(Expression numerator, Expression denominator) {
         this.numerator = numerator;
         this.denominator = denominator;
    }

    @Override
    public Expression takeDerivative() {
        return new Quotient(
                new Difference(
                        new Product(numerator.takeDerivative(), denominator),
                        new Product(numerator, denominator.takeDerivative()),
                    ),
                new Product(denominator, denominator)
            );
    }

    @Override
    public Operator getOperator() {
        return DIVISION;
    }

}

If you make separate classes for each operation, you don't need the switch anymore. Each class provides its own method instead.

class Quotient implements Expression {

    private final Expression numerator;
    private final Expression denominator;

    public Quotient(Expression numerator, Expression denominator) {
         this.numerator = numerator;
         this.denominator = denominator;
    }

    public Expression getNumerator() {
        return numerator;
    }

    public Expression getDenominator() {
        return denominator;
    }

    @Override
    public Expression takeDerivative() {
        return new Quotient(
                new Difference(
                        new Product(numerator.takeDerivative(), denominator),
                        new Product(numerator, denominator.takeDerivative()),
                    ),
                new Product(denominator, denominator)
            );
    }

    @Override
    public Expression simplify() {
        Expression n = numerator.simplify();
        Expression d = denominator.simplify();

        if (n.equals(Constant.ZERO) || d.equals(Constant.ONE)) {
            return n;
        }

        if (n.equals(d)) {
            return Constant.ONE;
        }

        if (d instanceof Quotient) {
            if (n instanceof Quotient) {
                return new Quotient(
                        new Product(n.getNumerator(), d.getDenominator()),
                        new Product(d.getNumerator(), n.getDenominator())
                    ).simplify();
            } else {
                return new Quotient(
                        new Product(n, d.getDenominator()),
                        d.getNumerator()
                    ).simplify();
            }
        } else if (n instanceof Quotient) {
            return new Quotient(
                    n.getNumerator(),
                    new Product(d, n.getDenominator())
                ).simplify();
        }

        return new Quotient(n, d);
    }

    @Override
    public Operator getOperator() {
        return DIVISION;
    }

    @Override
    public boolean equals(Object object) {
        if (! object instanceof Expression) {
            return false;
        }

        if (object == this) {
            return true;
        }

        Expression t = simplify();
        Expression o = ((Expression) object).simplify();

        if (! t instanceof Quotient) {
            return t.equals(o);
        }

        if (! o instanceof Quotient) {
            return false;
        }

        Quotient qt = (Quotient) t;
        Quotient qo = (Quotient) o;

        return qt.getNumerator().equals(qo.getNumerator())
                && qt.getDenominator().equals(qo.getDenominator());
    }

}

If you make separate classes for each operation, you don't need the switch anymore. Each class provides its own method instead. This can work for both takeDerivative and for simplify.

I did not implement hashCode, but it might be advisable to do so. Otherwise, funky things could happen if you used this as a key in a HashMap, put it in a HashSet, or otherwise did something that relied on hashCode.

I may be missing simplification cases. For example, \$\frac{x^2 + x}{x}\$ would not simplify to \$x+1 \forall x \ne 0\$ with this method. These are the ones that came to mind with easy implementations.

Source Link
mdfst13
  • 21.7k
  • 6
  • 33
  • 68

Restricted domain

    String operator;

Consider

    BinaryOperator operator;

Where

Enum BinaryOperator {

    PLUS("+"),
    MINUS("-"),
    MULTIPLY("*"),
    DIVIDE("/"),
    POWER("^");

    final private String display;

    BinaryOperator(String display) {
        this.display = display;
    }

    public String toString() {
        return display;
    }

}

This way, rather than having to do string evaluations to switch, you can just track the operations directly.

Naming

I would prefer a name like Expression to ExpNode. ExpNode tells the user how the structure is implemented. But a user shouldn't have to care about that. Logically, what you have is a representation of an expression.

A name like derivative is consistent with method names like iterator, but it is more typical to give methods verb names. E.g. calculateDerivative or takeDerivative.

Redundant fields

    OpNode mult1;
    OpNode mult2;
    OpNode mult3;
    OpNode div;
    OpNode plus;
    OpNode minus;
    OpNode exponent;
    NegateNode unary;

I didn't get why you had these at first. But reading through your derivative method, these are where you store the derivative. Consider instead

    private Expression derivative = null;

Then change from

            case "/": // h(x) = f(x)/g(x) then h′(x) = (f′(x)g(x) − f(x)g′(x))/( g(x)^2)
                mult1 = new OpNode("*");
                mult2 = new OpNode("*");
                mult3 = new OpNode("*");
                div = new OpNode("/");
                minus = new OpNode("-");
                mult1.setLeftChild(getRightChild());
                mult1.setRightChild(getLeftChild().derivative());
                mult2.setLeftChild(getLeftChild());
                mult2.setRightChild(getRightChild().derivative());
                minus.setLeftChild(mult1);
                minus.setRightChild(mult2);
                mult3.setLeftChild(getRightChild());
                mult3.setRightChild(getRightChild());
                div.setLeftChild(minus);
                div.setRightChild(mult3);
                return div;

to something like

            case "/":
                // h(x) = f(x)/g(x) then h′(x) = (f′(x)g(x) − f(x)g′(x))/( g(x)^2)
                Expression numerator = new BinaryExpression(DIFFERENCE);
                Expression denominator = new BinaryExpression(PRODUCT);

                Expression minuend = new BinaryExpression(PRODUCT);
                Expression subtrahend = new BinaryExpression(PRODUCT);

                minuend.setLeftChild(getLeftChild().takeDerivative());
                minuend.setRightChild(getRightChild());
                subtrahend.setLeftChild(getLeftChid());
                subtrahend.setRightChild(getRightChild().takeDerivative());

                numerator.setLeftChild(minuend);
                numerator.setRightChild(subtrahend);

                denominator.setLeftChild(getRightChild());
                denominator.setRightChild(getRightChild());

                derivative = new BinaryExpression(DIVISION);
                derivative.setLeftChild(numerator);
                derivative.setRightChild(denominator);

                return derivative;

Now you don't have to include every possible permutation as object fields. Only derivative persists past the length of the method. Everything else is a local variable specific to this operation.

I used operation specific names rather than generic names like left and right.

If you add

        if (derivative != null) {
            return derivative;
        }

Then you don't have to reprocess everything each time.

Or if you want to reprocess everything, then you can make derivative local as well.

More classes

You have a separate class for unary minus (NegateNode). Why not do that for other operations? Consider

class Quotient implements Expression {

    private final Expression numerator;
    private final Expression denominator;

    public Quotient(Expression numerator, Expression denominator) {
         this.numerator = numerator;
         this.denominator = denominator;
    }

    @Override
    public Expression takeDerivative() {
        return new Quotient(
                new Difference(
                        new Product(numerator.takeDerivative(), denominator),
                        new Product(numerator, denominator.takeDerivative()),
                    ),
                new Product(denominator, denominator)
            );
    }

    @Override
    public Operator getOperator() {
        return DIVISION;
    }

}

This may be missing a few methods, but it should cover the basics of the idea.

If you make separate classes for each operation, you don't need the switch anymore. Each class provides its own method instead.

A side issue is that this eliminates all those object fields entirely through simple construction.

I made this immutable. You may not want to do so. Easy enough to remove, as it's just a few final keywords.

I switched the object fields to be private rather than the default package private. You don't seem to be making use of the package private privileges, and this is usually better practice. Similarly, I explicitly made methods public rather than relying on them being called from the same package.