All Downloads are FREE. Search and download functionalities are using the official Maven repository.

info.bliki.wiki.template.expr.eval.DoubleEvaluator Maven / Gradle / Ivy

The newest version!
package info.bliki.wiki.template.expr.eval;

import info.bliki.wiki.template.expr.Parser;
import info.bliki.wiki.template.expr.SyntaxError;
import info.bliki.wiki.template.expr.ast.ASTNode;
import info.bliki.wiki.template.expr.ast.FunctionNode;
import info.bliki.wiki.template.expr.ast.NumberNode;
import info.bliki.wiki.template.expr.ast.SymbolNode;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Evaluate math expressions to double numbers.
 *
 * Use the expression syntax describe here: http://meta.wikimedia.org/wiki/Help:ParserFunctions#.23expr:
 */
public class DoubleEvaluator {
    /**
     * A "close to zero" double epsilon value for use to compare with 0.0
     *
     */
    public static double EPSILON = 1.0e-17d;

    /**
     * A "close to zero" double epsilon value for use in the Round function
     *
     */
    public static double EPSILON_ROUND = 1.0e-15d;

    Double bd;

    private static Map SYMBOL_DOUBLE_MAP;

    private static Map FUNCTION_DOUBLE_MAP;

    private static Map FUNCTION_BOOLEAN_MAP;

    static class PlusFunction implements IDoubleFunction, IDouble2Function {
        @Override
        public double evaluate(double arg1, double arg2) {
            return arg1 + arg2;
        }

        @Override
        public double evaluate(DoubleEvaluator engine, FunctionNode function) {
            double result = 0.0;
            for (int i = 1; i < function.size(); i++) {
                result += engine.evaluateNode(function.get(i));
            }
            return result;
        }
    }

    static class SubtractFunction implements IDoubleFunction, IDouble2Function {
        @Override
        public double evaluate(double arg1, double arg2) {
            return arg1 - arg2;
        }

        @Override
        public double evaluate(DoubleEvaluator engine, FunctionNode function) {
            double result = 0.0;
            for (int i = 1; i < function.size(); i++) {
                result += engine.evaluateNode(function.get(i));
            }
            return result;
        }
    }

    static class TimesFunction implements IDoubleFunction, IDouble2Function {
        @Override
        public double evaluate(double arg1, double arg2) {
            return arg1 * arg2;
        }

        @Override
        public double evaluate(DoubleEvaluator engine, FunctionNode function) {
            double result = 1.0;
            for (int i = 1; i < function.size(); i++) {
                result *= engine.evaluateNode(function.get(i));
            }
            return result;
        }
    }

    static class DivideFunction implements IDouble2Function {
        @Override
        public double evaluate(double arg1, double arg2) {
            if (Math.abs(arg2 - 0.0) < DoubleEvaluator.EPSILON) {
                throw new ArithmeticException("Division by zero");
            }
            return arg1 / arg2;
        }
    }

    static class ModFunction implements IDouble2Function {
        @Override
        public double evaluate(double arg1, double arg2) {
            if (Math.abs(arg2 - 0.0) < DoubleEvaluator.EPSILON) {
                throw new ArithmeticException("Division by zero");
            }
            return ((int) arg1) % ((int) arg2);
        }
    }

    static class PowFunction implements IDouble2Function {
        @Override
        public double evaluate(double arg1, double arg2) {
            return Math.pow(arg1, arg2);
        }

    }

    static {
        SYMBOL_DOUBLE_MAP = new HashMap<>();
        FUNCTION_BOOLEAN_MAP = new HashMap<>();
        SYMBOL_DOUBLE_MAP.put("E", new Double(Math.E));
        SYMBOL_DOUBLE_MAP.put("Pi", new Double(Math.PI));

        FUNCTION_BOOLEAN_MAP.put("And", new IBooleanBoolean2Function() {
            @Override
            public boolean evaluate(boolean arg1, boolean arg2) {
                return arg1 && arg2;
            }
        });
        FUNCTION_BOOLEAN_MAP.put("Not", new IBooleanBoolean1Function() {
            @Override
            public boolean evaluate(boolean arg1) {
                return !arg1;
            }
        });
        FUNCTION_BOOLEAN_MAP.put("Or", new IBooleanBoolean2Function() {
            @Override
            public boolean evaluate(boolean arg1, boolean arg2) {
                return arg1 || arg2;
            }
        });

        FUNCTION_BOOLEAN_MAP.put("Equal", new IBooleanDouble2Function() {
            @Override
            public boolean evaluate(double arg1, double arg2) {
                return Math.abs(arg1 - arg2) < EPSILON;
            }
        });
        FUNCTION_BOOLEAN_MAP.put("Greater", new IBooleanDouble2Function() {
            @Override
            public boolean evaluate(double arg1, double arg2) {
                return arg1 > arg2;
            }
        });
        FUNCTION_BOOLEAN_MAP.put("GreaterEqual", new IBooleanDouble2Function() {
            @Override
            public boolean evaluate(double arg1, double arg2) {
                return arg1 >= arg2;
            }
        });
        FUNCTION_BOOLEAN_MAP.put("Less", new IBooleanDouble2Function() {
            @Override
            public boolean evaluate(double arg1, double arg2) {
                return arg1 < arg2;
            }
        });
        FUNCTION_BOOLEAN_MAP.put("LessEqual", new IBooleanDouble2Function() {
            @Override
            public boolean evaluate(double arg1, double arg2) {
                return arg1 <= arg2;
            }
        });
        FUNCTION_BOOLEAN_MAP.put("Unequal", new IBooleanDouble2Function() {
            @Override
            public boolean evaluate(double arg1, double arg2) {
                return !(Math.abs(arg1 - arg2) < EPSILON);
            }
        });

        FUNCTION_DOUBLE_MAP = new HashMap<>();
        FUNCTION_DOUBLE_MAP.put("Sin", new IDouble1Function() {
            @Override
            public double evaluate(double arg1) {
                return Math.sin(arg1);
            }
        });
        FUNCTION_DOUBLE_MAP.put("Cos", new IDouble1Function() {
            @Override
            public double evaluate(double arg1) {
                return Math.cos(arg1);
            }
        });
        FUNCTION_DOUBLE_MAP.put("Tan", new IDouble1Function() {
            @Override
            public double evaluate(double arg1) {
                return Math.tan(arg1);
            }
        });
        FUNCTION_DOUBLE_MAP.put("ASin", new IDouble1Function() {
            @Override
            public double evaluate(double arg1) {
                return Math.asin(arg1);
            }
        });
        FUNCTION_DOUBLE_MAP.put("ACos", new IDouble1Function() {
            @Override
            public double evaluate(double arg1) {
                return Math.acos(arg1);
            }
        });
        FUNCTION_DOUBLE_MAP.put("ATan", new IDouble1Function() {
            @Override
            public double evaluate(double arg1) {
                return Math.atan(arg1);
            }
        });
        FUNCTION_DOUBLE_MAP.put("Ln", new IDouble1Function() {
            @Override
            public double evaluate(double arg1) {
                return Math.log(arg1);
            }
        });
        FUNCTION_DOUBLE_MAP.put("Exp", new IDouble1Function() {
            @Override
            public double evaluate(double arg1) {
                return Math.exp(arg1);
            }
        });
        FUNCTION_DOUBLE_MAP.put("Abs", new IDouble1Function() {
            @Override
            public double evaluate(double arg1) {
                return Math.abs(arg1);
            }
        });
        FUNCTION_DOUBLE_MAP.put("Ceil", new IDouble1Function() {
            @Override
            public double evaluate(double arg1) {
                return Math.ceil(arg1);
            }
        });
        FUNCTION_DOUBLE_MAP.put("Floor", new IDouble1Function() {
            @Override
            public double evaluate(double arg1) {
                return Math.floor(arg1);
            }
        });
        FUNCTION_DOUBLE_MAP.put("Trunc", new IDouble1Function() {
            @Override
            public double evaluate(double arg1) {
                if (arg1 < 0.0d) {
                    return Math.ceil(arg1);
                } else {
                    return Math.floor(arg1);
                }
            }
        });
        FUNCTION_DOUBLE_MAP.put("Plus", new PlusFunction());
        FUNCTION_DOUBLE_MAP.put("Subtract", new SubtractFunction());
        FUNCTION_DOUBLE_MAP.put("Times", new TimesFunction());
        FUNCTION_DOUBLE_MAP.put("Divide", new DivideFunction());
        FUNCTION_DOUBLE_MAP.put("Mod", new ModFunction());
        FUNCTION_DOUBLE_MAP.put("Pow", new PowFunction());

        FUNCTION_DOUBLE_MAP.put("Round", new IDouble2Function() {
            @Override
            public double evaluate(double arg1, double arg2) {
                double pow10arg2 = Math.pow(10, arg2);
                if (arg1 > 0) {
                    return Math.round((arg1 + EPSILON_ROUND) * pow10arg2) / pow10arg2;
                } else {
                    return Math.round((arg1 - EPSILON_ROUND) * pow10arg2) / pow10arg2;
                }
            }
        });
    }

    private ASTNode fNode;

    public DoubleEvaluator() {
        this(null);
    }

    public DoubleEvaluator(ASTNode node) {
        fNode = node;
    }

    /**
     * Parse the given expression String and store the resulting
     * ASTNode in this DoubleEvaluator
     *
     * @param expression
     * @return
     * @throws SyntaxError
     */
    public ASTNode parse(String expression) {
        Parser p = new Parser();
        fNode = p.parse(expression);
        if (fNode instanceof FunctionNode) {
            fNode = optimizeFunction((FunctionNode) fNode);
        }
        return fNode;
    }

    /**
     * Parse the given expression String and return the resulting
     * ASTNode
     *
     * @param expression
     * @return
     * @throws SyntaxError
     */
    public static ASTNode parseNode(String expression) {
        DoubleEvaluator doubleEvaluator = new DoubleEvaluator();
        return doubleEvaluator.parse(expression);
    }

    public double evalStep(String expression) {
        Parser p = new Parser();
        fNode = p.parse(expression);
        if (fNode instanceof FunctionNode) {
            fNode = optimizeFunction((FunctionNode) fNode);
        }
        return evaluateNode(fNode);
    }

    private List splitByBrackets(String expression) {
        ArrayList sp = new ArrayList<>();
        int level = 0;
        int lastPos = 0;
        int startPos = -1;
        for (int i = 0; i < expression.length(); i++) {
            if (expression.charAt(i) == '(') {
                if (level == 0) {
                    sp.add(expression.substring(lastPos, i));
                    lastPos = i;
                    startPos = i;
                }
                level++;
            } else if (expression.charAt(i) == ')') {
                level--;
                if (level == 0 && startPos >= 0) {
                    lastPos = i + 1;
                    double val = evaluate(expression.substring(startPos + 1, i));
                    sp.add(Double.toString(val));
                }
            }
        }
        if (level == 0) {
            if (lastPos == 0) {
                double val = evalStep(expression.substring(0, expression.length()));
                sp.add(Double.toString(val));
            } else {
                sp.add(expression.substring(lastPos, expression.length()));
            }
        }
        return sp;
    }

    /**
     * Parse the given expression String and evaluate it to a double
     * value
     *
     * @param expression
     * @return
     * @throws SyntaxError
     */
    public double evaluate(String expression) {
        List list = splitByBrackets(expression);
        int listSize = list.size();
        while (listSize > 1) {
            int len = 0;
            for (int i = 0; i < listSize; i++) {
                len += list.get(i).length();
            }
            StringBuilder buf = new StringBuilder(len);
            for (int i = 0; i < listSize; i++) {
                buf.append(list.get(i));
            }
            list = splitByBrackets(buf.toString());
            listSize = list.size();
        }
        return Double.parseDouble(list.get(0));
    }

    /**
     * Reevaluate the expression (possibly after a new Variable
     * assignment)
     *
     * @param expression
     * @return
     * @throws SyntaxError
     */
    public double evaluate() {
        if (fNode == null) {
            throw new SyntaxError(0, 0, 0, " ", "No parser input defined", 1);
        }
        return evaluateNode(fNode);
    }

    /**
     * Evaluate an already parsed in abstract syntax tree node into a
     * double number value.
     *
     * @param node
     *          abstract syntax tree node
     *
     * @return the evaluated double number
     *
     * @throws ArithmeticException
     *           if the node cannot be evaluated.
     */
    public double evaluateNode(final ASTNode node) {
        if (node instanceof DoubleNode) {
            return ((DoubleNode) node).doubleValue();
        }
        if (node instanceof FunctionNode) {
            return evaluateFunction((FunctionNode) node);
        }
        if (node instanceof SymbolNode) {
            Double dbl = SYMBOL_DOUBLE_MAP.get(node.toString());
            if (dbl != null) {
                return dbl.doubleValue();
            }
        }
        if (node instanceof NumberNode) {
            return ((NumberNode) node).doubleValue();
        }

        throw new ArithmeticException("EvalDouble#evaluate(ASTNode) not possible for: " + node.toString());
    }

    /**
     * Evaluate an already parsed in FunctionNode into a
     * souble number value.
     *
     * @param functionNode
     * @return
     *
     * @throws ArithmeticException
     *           if the functionNode cannot be evaluated.
     */
    public double evaluateFunction(final FunctionNode functionNode) {
        if (functionNode.size() > 0 && functionNode.get(0) instanceof SymbolNode) {
            String symbol = functionNode.get(0).toString();
            if (functionNode.size() == 1) {
                Object obj = FUNCTION_DOUBLE_MAP.get(symbol);
                if (obj instanceof IDouble0Function) {
                    return ((IDouble0Function) obj).evaluate();
                }
            } else if (functionNode.size() == 2) {
                Object obj = FUNCTION_DOUBLE_MAP.get(symbol);
                if (obj instanceof IDouble1Function) {
                    return ((IDouble1Function) obj).evaluate(evaluateNode(functionNode.get(1)));
                }
                return evaluateNodeLogical(functionNode) ? 1.0 : 0.0;
            } else if (functionNode.size() == 3) {
                Object obj = FUNCTION_DOUBLE_MAP.get(symbol);
                if (obj instanceof IDouble2Function) {
                    return ((IDouble2Function) obj).evaluate(evaluateNode(functionNode.get(1)), evaluateNode(functionNode.get(2)));
                }
                return evaluateNodeLogical(functionNode) ? 1.0 : 0.0;
            } else {
                Object obj = FUNCTION_DOUBLE_MAP.get(symbol);
                if (obj instanceof IDoubleFunction) {
                    return ((IDoubleFunction) obj).evaluate(this, functionNode);
                }
            }
        }
        throw new ArithmeticException("EvalDouble#evaluateFunction(FunctionNode) not possible for: " + functionNode.toString());
    }

    public boolean evaluateNodeLogical(final ASTNode node) {
        if (node instanceof FunctionNode) {
            return evaluateFunctionLogical((FunctionNode) node);
        }
        if (node instanceof DoubleNode) {
            double d = ((DoubleNode) node).doubleValue();
            return !(Math.abs(d - 0.0) < DoubleEvaluator.EPSILON);
        }
        if (node instanceof NumberNode) {
            double d = ((NumberNode) node).doubleValue();
            return !(Math.abs(d - 0.0) < DoubleEvaluator.EPSILON);
        }

        throw new ArithmeticException("EvalDouble#evaluateNodeLogical(ASTNode) not possible for: " + node.toString());
    }

    public boolean evaluateFunctionLogical(final FunctionNode functionNode) {
        if (functionNode.size() > 0 && functionNode.get(0) instanceof SymbolNode) {
            String symbol = functionNode.get(0).toString();
            if (functionNode.size() == 2) {
                Object obj = FUNCTION_BOOLEAN_MAP.get(symbol);
                if (obj instanceof IBooleanBoolean1Function) {
                    return ((IBooleanBoolean1Function) obj).evaluate(evaluateNodeLogical(functionNode.get(1)));
                }
            } else if (functionNode.size() == 3) {
                Object obj = FUNCTION_BOOLEAN_MAP.get(symbol);
                if (obj instanceof IBooleanDouble2Function) {
                    return ((IBooleanDouble2Function) obj).evaluate(evaluateNode(functionNode.get(1)), evaluateNode(functionNode.get(2)));
                } else if (obj instanceof IBooleanBoolean2Function) {
                    return ((IBooleanBoolean2Function) obj).evaluate(evaluateNodeLogical(functionNode.get(1)),
                            evaluateNodeLogical(functionNode.get(2)));
                }
            }
        }
        throw new ArithmeticException("EvalDouble#evaluateFunctionLogical(FunctionNode) not possible for: " + functionNode.toString());

    }

    /**
     * Optimize an already parsed in functionNode into an
     * ASTNode.
     *
     * @param functionNode
     * @return
     *
     */
    public ASTNode optimizeFunction(final FunctionNode functionNode) {
        if (functionNode.size() > 0) {
            boolean doubleOnly = true;
            ASTNode node;
            for (int i = 1; i < functionNode.size(); i++) {
                node = functionNode.get(i);
                if (node instanceof NumberNode) {
                    functionNode.set(i, new DoubleNode(((NumberNode) functionNode.get(i)).doubleValue()));
                } else if (functionNode.get(i) instanceof FunctionNode) {
                    ASTNode optNode = optimizeFunction((FunctionNode) functionNode.get(i));
                    if (!(optNode instanceof DoubleNode)) {
                        doubleOnly = false;
                    }
                    functionNode.set(i, optNode);
                } else {
                    doubleOnly = false;
                }
            }
            if (doubleOnly) {
                try {
                    return new DoubleNode(evaluateFunction(functionNode));
                } catch (Exception e) {

                }
            }
        }
        return functionNode;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy