/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package jmathexpr.arithmetic.op;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import jmathexpr.AbstractExpression;
import jmathexpr.Constant;
import jmathexpr.Expression;
import jmathexpr.Precedence;
import jmathexpr.Variable;
import jmathexpr.arithmetic.ANumber;
import jmathexpr.arithmetic.Polynomial;
import jmathexpr.arithmetic.func.Lcm;
import jmathexpr.arithmetic.natural.Naturals;
import jmathexpr.arithmetic.pattern.FractionPattern;
import jmathexpr.arithmetic.pattern.NumberPattern;
import jmathexpr.arithmetic.real.Reals;
import jmathexpr.op.Operation;
import jmathexpr.set.Set;
import jmathexpr.util.logging.Logger;
import jmathexpr.util.pattern.AbstractPattern;
import jmathexpr.util.pattern.AnyPattern;
import jmathexpr.util.pattern.ExpressionPattern;
import jmathexpr.util.pattern.FunctionPattern;
import jmathexpr.util.pattern.TerminationPattern;
import jmathexpr.util.rule.Rule;
/**
* Sum of multiple (one, two or more) terms using either the plus or the minus
* sign.
*
* @author Elemér Furka
*/
public class Sum extends AbstractExpression implements Operation, ExpressionPattern {
private final List<Expression> terms;
/**
* Creates a new Sum instance decomposing the given expression into additive
* terms.
*
* @param expr an arbitrary expression
*/
public Sum(Expression expr) {
terms = new ArrayList();
add(expr);
}
public Sum(Expression... terms) {
this.terms = new ArrayList();
for (Expression t : terms) {
add(t);
}
}
private Sum(List<Expression> operands) {
terms = operands;
}
/**
* Factory method that creates a new Sum instance by adding the two
* arguments. Both arguments are extracted if possible.
*
* @param augend the augend
* @param addend the addend
* @return a new Addition or Sum instance
*/
public static Operation add(Expression augend, Expression addend) {
Sum sum = new Sum(augend);
sum.add(addend);
if (sum.getArity() == 2) {
return new Addition(augend, addend);
} else {
return sum;
}
}
/**
* Factory method that creates a new Sum instance by subtracting the second
* argument from the first one. Both arguments are extracted if possible.
*
* @param minuend the minuend
* @param subtrahend the subtrahend
* @return a new Subtraction or Sum instance
*/
public static Operation subtract(Expression minuend, Expression subtrahend) {
Sum sum = new Sum(minuend);
sum.subtract(subtrahend);
if (sum.getArity() == 2) {
return new Subtraction(minuend, subtrahend);
} else {
return sum;
}
}
private void add(Sum sum) {
for (Expression t : sum.terms) {
add(t);
}
}
private void add(Addition addition) {
add(addition.lhs());
add(addition.rhs());
}
private void add(Subtraction subtraction) {
add(subtraction.lhs());
subtract(subtraction.rhs());
}
private void add(Expression expr) {
if (expr instanceof Addition) {
add((Addition) expr);
} else if (expr instanceof Sum) {
add((Sum) expr);
} else if (expr instanceof Subtraction) {
add((Subtraction) expr);
} else {
terms.add(expr);
}
}
private void subtract(Expression expr) {
if (expr instanceof Addition) {
subtract((Addition) expr);
} else if (expr instanceof Sum) {
subtract((Sum) expr);
} else if (expr instanceof Subtraction) {
subtract((Subtraction) expr);
} else {
terms.add(new Negation(expr));
}
}
private void subtract(Addition addition) {
subtract(addition.lhs());
subtract(addition.rhs());
}
private void subtract(Sum sum) {
for (Expression t : sum.terms) {
subtract(t);
}
}
private void subtract(Subtraction subtraction) {
subtract(subtraction.lhs());
add(subtraction.rhs());
}
@Override
public List<Expression> operands() {
return Collections.unmodifiableList(terms);
}
@Override
public List<Expression> getChildren() {
return operands();
}
@Override
public Expression evaluate() {
Expression simplified = simplify();
if (!(simplified instanceof Sum)) return simplified;
List<Expression> evaluated = ((Sum) simplified).terms;
Map<Expression, List<ANumber>> constants = selectConstants(evaluated);
Expression constant = combineConstants(constants);
if (constants.size() == terms.size()) {
return constant;
}
Map<Expression, List<Expression>> expressionMap = selectLikeTerms(evaluated);
return build(constant, expressionMap);
}
private Expression simplify() {
List<Expression> evaluated = evaluate(terms);
List<Expression> denominators = new ArrayList();
FractionPattern p = new FractionPattern();
for (Expression t : evaluated) {
if (p.matches(t)) {
denominators.add(p.denominator());
}
}
if (!denominators.isEmpty()) { // summing fractions
Expression lcd = new Lcm(denominators).evaluate();
return new Division(multiply(lcd), lcd).evaluate();
}
return new Sum(evaluated);
}
private Expression build(Expression constant, Map<Expression, List<Expression>> expressionMap) {
Sum sum = null;
Map<Expression, List<ANumber>> constants;
Expression coeff;
if (!expressionMap.isEmpty()) {
sum = new Sum();
for (Expression e : expressionMap.keySet()) {
constants = selectConstants(expressionMap.get(e));
coeff = combineConstants(constants);
if (coeff instanceof ANumber && ((ANumber) coeff).isOne()) { // c = 1
sum.add(e);
} else if (coeff instanceof ANumber && ((ANumber) coeff).negate().isOne()) { // c = -1
sum.add(new Negation(e));
} else if (!(coeff instanceof ANumber && ((ANumber) coeff).isZero())) { // c <> 0
sum.add(new Multiplication(coeff, e));
}
}
}
if (!(constant instanceof ANumber && ((ANumber) constant).isZero())) {
if (sum == null) {
sum = new Sum(constant);
} else {
sum.add(constant);
}
}
if (sum == null) {
return Naturals.zero();
}
return sum.toSimpleExpression();
}
private Expression toSimpleExpression() {
if (terms.size() == 1) {
return terms.get(0);
} else if (terms.size() == 2) {
Expression a = terms.get(0);
Expression b = terms.get(1);
if (b instanceof Negation) {
return new Subtraction(a, ((Negation) b).getChild());
} else if (b instanceof ANumber && ((ANumber) b).isNegative()) {
return new Subtraction(a, ((ANumber) b).negate());
} else {
return new Addition(a, b);
}
} else {
return this;
}
}
private List<Expression> evaluate(List<Expression> unevaluated) {
List<Expression> result = new ArrayList();
Expression evaluated;
for (Expression t : unevaluated) {
evaluated = t.evaluate();
if (evaluated instanceof Addition) {
result.add(((Addition) evaluated).lhs());
result.add(((Addition) evaluated).rhs());
} else if (evaluated instanceof Subtraction) {
result.add(((Subtraction) evaluated).lhs());
result.add(toMultiplication(
new Negation(((Subtraction) evaluated).rhs()).evaluate()));
} else if (evaluated instanceof Sum) {
for (Expression e : ((Sum) evaluated).terms) {
result.add(e);
}
} else if (evaluated instanceof Negation) {
result.add(toMultiplication(evaluated));
} else {
result.add(evaluated);
}
}
return result;
}
private static Expression toMultiplication(Expression negation) {
if (negation instanceof Negation) {
return ((Negation) negation).toMultiplication();
} else {
return negation;
}
}
private Map<Expression, List<ANumber>> selectConstants(List<Expression> evaluated) {
Map<Expression, List<ANumber>> selected = new HashMap(); // ai*c -> a1, a2, ... an
NumberPattern a = new NumberPattern();
TerminationPattern c = new Constant();
ExpressionPattern p = new Multiplication(a, c);
for (Expression e : evaluated) {
if (e.isConstant()) {
if (e instanceof ANumber) { // 1 -> numbers
addToSelectionMap(selected, Naturals.one(), (ANumber) e);
} else if (p.matches(e)) {
addToSelectionMap(selected, c.hit(), a.hit());
} else {
addToSelectionMap(selected, e, Naturals.one());
}
}
}
return selected;
}
private Map<Expression, List<Expression>> selectLikeTerms(List<Expression> evaluated) {
Map<Expression, List<Expression>> selected = new HashMap(); // ai*f(x) -> a1, a2, ... an
TerminationPattern c = new Constant();
TerminationPattern f = new FunctionPattern(new Variable());
ExpressionPattern p = new Multiplication(c, f);
for (Expression e : evaluated) {
if (e.isConstant()) {
continue;
} else if (p.matches(e)) {
addToSelectionMap(selected, f.hit(), c.hit());
} else {
addToSelectionMap(selected, e, Naturals.one());
}
}
return selected;
}
private static <E extends Expression> void addToSelectionMap(
Map<Expression, List<E>> selected, Expression key, E value) {
if (selected.containsKey(key)) {
selected.get(key).add(value);
} else {
List<E> list = new ArrayList();
list.add(value);
selected.put(key, list);
}
}
private Expression combineConstants(Map<Expression, List<ANumber>> selected) {
Sum combined = new Sum();
ANumber sum;
for (Expression c : selected.keySet()) {
sum = Naturals.zero();
for (ANumber a : selected.get(c)) {
sum = sum.add(a);
}
if (sum.isOne()) {
combined.add(c);
} else if (!sum.isZero()) {
if (c instanceof ANumber) { // c = 1
combined.add(sum);
} else if (sum.negate().isOne()) {
combined.add(new Negation(c));
} else {
combined.add(new Multiplication(sum, c));
}
}
}
if (combined.terms.isEmpty()) {
return Naturals.zero();
}
return combined.toSimpleExpression();
}
@Override
public Precedence getPrecedence() {
return Precedence.Addition;
}
@Override
public int getArity() {
return terms.size();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
for (Expression t : terms) {
if (builder.length() == 0) {
builder.append(t);
} else if (t instanceof Negation) {
builder.append(" - " + ((Negation) t).getChild());
} else {
builder.append(" + " + t);
}
}
return builder.toString();
}
@Override
public String toUnicode() {
return toString();
}
@Override
public boolean equals(Object object) {
if (object == null) return false;
if (this == object) return true;
if (!(object instanceof Sum)) return false;
Sum other = (Sum) object;
if (terms.size() != other.terms.size()) return false;
for (int i = 0; i < terms.size(); i++) {
if (!terms.get(i).equals(other.terms.get(i))) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
int hash = 3;
hash = 53 * hash + Objects.hashCode(this.terms);
return hash;
}
@Override
public Set domain() {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public Set codomain() {
return Reals.getInstance();
}
@Override
public boolean isConstant() {
for (Expression t : terms) {
if (!t.isConstant()) {
return false;
}
}
return true;
}
@Override
public boolean contains(ExpressionPattern pattern) {
if (pattern instanceof Sum) {
Sum p = (Sum) pattern;
boolean matches = true;
if (terms.size() == p.terms.size()) {
Expression t;
for (int i = 0; matches && i < terms.size(); i++) {
t = terms.get(i);
if (!t.contains((ExpressionPattern) p.terms.get(i))) {
matches = false;
}
}
}
if (matches) {
return true;
}
}
for (Expression t : terms) {
if (t.contains(pattern)) {
return true;
}
}
return false;
}
@Override
public boolean isApplicable(Rule rule) {
boolean isApplicable = false;
List<Expression> subs = new ArrayList();
Expression t;
subs.addAll(terms);
for (int i = 0; i < subs.size(); i++) {
t = subs.get(i);
if (t.isApplicable(rule)) {
isApplicable = true;
if (rule.isRecursive()) {
subs.add(i, rule.subapply());
}
}
}
Expression transformed = isApplicable && rule.isRecursive() ? new Sum(subs) : this;
if (rule.matches(transformed)) {
return true;
} else if (isApplicable) {
rule.register(transformed);
}
return isApplicable;
}
/**
* Converts this sum into a polynomial.
*
* @param x the indeterminate
* @return a newly created polynomial instance
*/
public Polynomial toPolynomial(Variable x) {
return new Polynomial(terms, x);
}
/**
* Used to test other additive operation patterns (Addition and Subtraction).
*
* @param pattern an Addition or Subtraction instance
* @return true if this sum matches the given pattern
*/
public boolean matches(ExpressionPattern pattern) {
if (pattern instanceof Addition) {
Addition p = (Addition) pattern;
return terms.size() == 2
&& ((ExpressionPattern) p.lhs()).matches(terms.get(0))
&& ((ExpressionPattern) p.rhs()).matches(terms.get(1));
} else if (pattern instanceof Subtraction) {
Subtraction p = (Subtraction) pattern;
if (terms.size() == 2) {
if (!((ExpressionPattern) p.lhs()).matches(terms.get(0))) {
return false;
}
Expression t1 = terms.get(1);
if (t1 instanceof Negation) {
return ((ExpressionPattern) p.rhs()).matches(((Negation) t1).getChild());
}
}
return false;
} else {
throw new IllegalArgumentException("Unexpected pattern: " + pattern);
}
}
@Override
public boolean matches(Expression expr) {
return new Matcher().matches(expr);
}
/**
* Multiplies all terms of this sum by the given expression from left.
*
* @param expr an arbitrary expression
* @return a new Sum instance having its all terms multiplied by the
* specified expression
*/
public Sum multiply(Expression expr) {
List<Expression> multiplied = new ArrayList();
for (Expression t : terms) {
multiplied.add(new Multiplication(expr, t));
}
return new Sum(multiplied);
}
/**
* Negates this sum by negating each of its terms.
*
* @return a new Sum instance having negated terms
*/
public Sum negate() {
List<Expression> negated = new ArrayList();
for (Expression t : terms) {
negated.add(new Negation(t));
}
return new Sum(negated);
}
private class Matcher extends AbstractPattern {
private final List<Expression> expressions = new ArrayList();
/**
* Pattern index -> matching expression
*/
private final Map<Integer, Expression> matchingPatterns = new HashMap();
@Override
public boolean matches(Expression expr) {
if (expr instanceof Addition) {
expressions.add(((Addition) expr).lhs());
expressions.add(((Addition) expr).rhs());
if (terms.size() == 2) {
return hasMatchingTerm() && hasMatchingTerm();
}
} else if (expr instanceof Subtraction) {
expressions.add(((Subtraction) expr).lhs());
expressions.add(new Negation(((Subtraction) expr).rhs()));
if (terms.size() == 2) {
return hasMatchingTerm() && hasMatchingTerm();
}
} else if (expr instanceof Sum) {
if (terms.size() > ((Sum) expr).terms.size()) {
return false;
}
for (Expression t : ((Sum) expr).terms) {
expressions.add(t);
}
for (int i = 0; i < terms.size(); i++) {
if (!hasMatchingTerm()) {
return false;
}
}
return true;
}
return false;
}
private boolean hasMatchingTerm() {
ExpressionPattern p;
Expression e;
for (int i = 0; i < terms.size(); i++) { // looping over the patterns...
if (matchingPatterns.containsKey(i)) {
continue;
}
p = (ExpressionPattern) terms.get(i);
if (p instanceof AnyPattern && i == terms.size() - 1) {
Sum rest = new Sum(expressions);
return p.matches(rest);
}
for (int j = 0; j < expressions.size(); j++) { // ... to find a matching term
e = expressions.get(j);
if (p.matches(e)) {
matchingPatterns.put(i, e);
expressions.remove(j);
return true;
}
}
return false;
}
return false;
}
}
}