How to use java to handle string formula operations

Source: Internet
Author: User
Tags class operator

When improving a contract project, there is a need because the calculation formula for non-data items in the contract will be changed according to the year, and the formula was previously hard coded into the system, as long as the time changes, the system will not be able to use it. Therefore, all non-basic data items in the contract must be able to customize the formula and generate reports and data in the contract according to the set formula.

Obviously, all the defined formulas are stored in the database as strings, but java does not have such tools or classes to execute the string formula, and an intermediate formula can be nested in the formula. For example, the basic data dddd is 56, while a formula depends on dddd. eeee = dddd * 20, and the final formula may be: eeee *-12 + 13-dddd + 24. We can see that eeee is an intermediate formula, so the calculation of a formula requires the intermediate formula and basic data.

This seems to be able to be solved using an interpreter mode, but I have not succeeded, because the priority of parentheses is a tricky problem, later I thought that I could use a template engine similar to freemarker or the ScriptEngine Script Engine provided after Java 6. I did an experiment and the script engine could solve the problem, however, this restricts the use of Java 6 and later versions. Finally, I found the perfect solution, that is, the suffix expression. The formula we usually write is called an infix expression, which is difficult for computers to process. Therefore, we need to convert the infix expression into a suffix expression that is easier to process on computers.

Convert an infix expression to a suffix expression. Specific algorithm rules: see suffix expressions.

A. If it is '(', inbound stack;

B. If it is ')', add the operators in the stack to the suffix expression in sequence until '(' appears, and delete '(' from the stack '(';

C. If it is an operator other than parentheses, when its priority is higher than the top operator of the stack, it is directly written into the stack. Otherwise, from the top of the stack, an operator with a higher priority and a higher priority than the operator currently processed will pop up until one operator with a lower priority or a left bracket is encountered.

· When the infix expression of the scan ends, all operators in the stack exit the stack;

Our requirements are as follows:

Copy codeThe Code is as follows: public class FormulaTest {
@ Test
Public void testFormula (){
// Basic data
Map <String, BigDecimal> values = new HashMap <String, BigDecimal> ();
Values. put ("dddd", BigDecimal. valueOf (56d ));

// Other formulas to be dependent
Map <String, String> formulas = new HashMap <String, String> ();
Formulas. put ("eeee", "# {dddd} * 20 ");

// Formula to be calculated
String expression = "# {eeee} *-12 + 13-# {dddd} + 24 ";

BigDecimal result = FormulaParser. parse (expression, formulas, values );
Assert. assertEquals (result, BigDecimal. valueOf (-13459.0 ));
}
}

The following are the steps to solve the problem:

1. Replace all the intermediate variables with the basic data.

The finalExpression method of FormulaParser replaces all the intermediate variables with the basic data, which is a recursive method.

Copy codeThe Code is as follows: public class FormulaParser {
/**
* Regular Expressions matching placeholder Variables
*/
Private static Pattern pattern = Pattern. compile ("\\#\{ (. + ?) \\}");

/**
* Parse the formula and perform the formula calculation.
*
* @ Param formula
* @ Param formulas
* @ Param values
* @ Return
*/
Public static BigDecimal parse (String formula, Map <String, String> formulas, Map <String, BigDecimal> values ){
If (formulas = null) formulas = Collections. emptyMap ();
If (values = null) values = Collections. emptyMap ();
String expression = finalExpression (formula, formulas, values );
Return new Calculator (). eval (expression );
}

/**
* Parse the formula and perform the formula calculation.
*
* @ Param formula
* @ Param values
* @ Return
*/
Public static BigDecimal parse (String formula, Map <String, BigDecimal> values ){
If (values = null) values = Collections. emptyMap ();
Return parse (formula, Collections. <String, String> emptyMap (), values );
}

/**
* Parse the formula and perform the formula calculation.
*
* @ Param formula
* @ Return
*/
Public static BigDecimal parse (String formula ){
Return parse (formula, Collections. <String, String> emptyMap (), Collections. <String, BigDecimal> emptyMap ());
}

/**
* Replace all the intermediate variables with the basic data.
*
* @ Param expression
* @ Param formulas
* @ Param values
* @ Return
*/
Private static String finalExpression (String expression, Map <String, String> formulas, Map <String, BigDecimal> values ){
Matcher m = pattern. matcher (expression );
If (! M. find () return expression;

M. reset ();

StringBuffer buffer = new StringBuffer ();
While (m. find ()){
String group = m. group (1 );
If (formulas! = Null & formulas. containsKey (group )){
String formula = formulas. get (group );
M. appendReplacement (buffer, '(' + formula + ')');
} Else if (values! = Null & values. containsKey (group )){
BigDecimal value = values. get (group );
M. appendReplacement (buffer, value. toPlainString ());
} Else {
Throw new IllegalArgumentException ("expression'" + expression + "'has a illegal variable:" + m. group () + ", cause retriable '" + group + "' not being found in formulas or in values. ");
}
}
M. appendTail (buffer );
Return finalExpression (buffer. toString (), formulas, values );
}
}

2. convert an infix expression to a suffix expression.

Infix2Suffix of Calculator converts the infix expression into a suffix expression.

3. Calculate the suffix expression

The evalInfix of Calculator calculates the suffix expression.

Copy codeThe Code is as follows: public class Calculator {
Private static Log logger = LogFactory. getLog (Calculator. class );

/**
* Left parenthesis
*/
Public final static char LEFT_BRACKET = '(';

/**
* Parentheses
*/
Public final static char RIGHT_BRACKET = ')';

/**
* Ignore spaces in the infix expression.
*/
Public final static char BLANK = '';

/**
* Decimal point
*/
Public final static char DECIMAL_POINT = '.';

/**
* Negative number
*/
Public final static char NEGATIVE_SIGN = '-';

/**
* Positive ID
*/
Public final static char POSITIVE_SIGN = '+ ';

/**
* Delimiter of each segment of the suffix expression
*/
Public final static char SEPARATOR = '';

/**
* Parse and calculate the expression
*
* @ Param expression
* @ Return
*/
Public BigDecimal eval (String expression ){
String str = infix2Suffix (expression );
Logger.info ("Infix Expression:" + expression );
Logger.info ("Suffix Expression:" + str );
If (str = null ){
Throw new IllegalArgumentException ("Infix Expression is null! ");
}
Return evalInfix (str );
}

/**
* Suffix expression Calculation
*
* @ Param expression
* @ Return
*/
Private BigDecimal evalInfix (String expression ){
String [] strs = expression. split ("\ s + ");
Stack <String> stack = new Stack <String> ();
For (int I = 0; I <strs. length; I ++ ){
If (! Operator. isOperator (strs [I]) {
Stack. push (strs [I]);
} Else {
Operator op = Operator. getInstance (strs [I]);
BigDecimal right = new BigDecimal (stack. pop ());
BigDecimal left = new BigDecimal (stack. pop ());
BigDecimal result = op. eval (left, right );
Stack. push (String. valueOf (result ));
}
}
Return new BigDecimal (stack. pop ());
}

/**
* Converting an infix expression to a suffix expression <br>
* Specific algorithm Rules 81*1) Computer conversion: the idea of converting an infix expression into a suffix expression:
* Start scanning;
* Adds a suffix expression to a number;
* Operator:
* A. If it is '(', it is added to the stack;
* B. If it is ')', add the operators in the stack to the suffix expression in sequence until '(' is displayed, and delete '(' from the stack '(';
* C. If it is an operator other than parentheses, it is directly written into the stack when its priority is higher than the stack top operator. Otherwise, from the top of the stack, an operator with a higher priority and a higher priority than the operator currently processed will pop up until one operator with a lower priority or a left bracket is encountered.
* When the infix expression of the scan ends, all operators in the stack exit the stack;
*
* @ Param expression
* @ Return
*/
Public String infix2Suffix (String expression ){
If (expression = null) return null;

Stack <Character> stack = new Stack <Character> ();

Char [] chs = expression. toCharArray ();
StringBuilder sb = new StringBuilder (chs. length );

Boolean appendSeparator = false;
Boolean sign = true;
For (int I = 0; I <chs. length; I ++ ){
Char c = chs [I];

// Skip if it is blank
If (c = BLANK) continue;

// Next line is used output stack information.
// System. out. printf ("%-20 s % n", stack, sb. toString ());

// Add a suffix expression Separator
If (appendSeparator ){
Sb. append (SEPARATOR );
AppendSeparator = false;
}

If (isSign (c) & sign ){
Sb. append (c );
} Else if (isNumber (c )){
Sign = false; // a number is not followed by a positive or negative sign, but an operator +-
Sb. append (c );
} Else if (isLeftBracket (c )){
Stack. push (c );
} Else if (isRightBracket (c )){
Sign = false;

// If it is), it will pop up (all the operators above, and add them to the suffix expression, and pop up (
While (stack. peek ()! = LEFT_BRACKET ){
Sb. append (SEPARATOR). append (stack. pop ());
}
Stack. pop ();
} Else {
AppendSeparator = true;
If (Operator. isOperator (c )){
Sign = true;

// If yes
If (stack. isEmpty () | stack. peek () = LEFT_BRACKET ){
Stack. push (c );
Continue;
}
Int precedence = Operator. getPrority (c );
While (! Stack. isEmpty () & Operator. getPrority (stack. peek ()> = precedence ){
Sb. append (SEPARATOR). append (stack. pop ());
}
Stack. push (c );
}
}
}
While (! Stack. isEmpty ()){
Sb. append (SEPARATOR). append (stack. pop ());
}
Return sb. toString ();
}

/**
* Determines whether a character is a positive or negative sign.
*
* @ Param c
* @ Return
*/
Private boolean isSign (char c ){
Return (c = NEGATIVE_SIGN | c = POSITIVE_SIGN );
}

/**
* Determines whether a character is a number or decimal point
*
* @ Param c
* @ Return
*/
Private boolean isNumber (char c ){
Return (c> = '0' & c <= '9') | c = DECIMAL_POINT );
}

/**
* Determines whether a character is left parenthesis.
*
* @ Param c
* @ Return
*/
Private boolean isLeftBracket (char c ){
Return c = LEFT_BRACKET;
}

/**
* Determines whether a character is in parentheses.
*
* @ Param c
* @ Return
*/
Private boolean isRightBracket (char c ){
Return c = RIGHT_BRACKET;
}

Finally, paste the operator classCopy codeThe Code is as follows: View Code
Public abstract class Operator {
/**
* Operator
*/
Private char operator;

/**
* Operator priority. The greater the number, the higher the priority.
*/
Private int priority;

Private static Map <Character, Operator> operators = new HashMap <Character, Operator> ();

Private Operator (char operator, int priority ){
SetOperator (operator );
SetPriority (priority );
Register (this );
}

Private void register (Operator operator ){
Operators. put (operator. getOperator (), operator );
}

/**
* Addition operation
*/
Public final static Operator ADITION = new Operator ('+', 100 ){
Public BigDecimal eval (BigDecimal left, BigDecimal right ){
Return left. add (right );
}
};

/**
* Subtraction
*/
Public final static Operator SUBTRATION = new Operator ('-', 100 ){
Public BigDecimal eval (BigDecimal left, BigDecimal right ){
Return left. subtract (right );
}
};

/**
* Multiplication
*/
Public final static Operator MULTIPLICATION = new Operator ('*', 200 ){
Public BigDecimal eval (BigDecimal left, BigDecimal right ){
Return left. multiply (right );
}
};

/**
* Division operation
*/
Public final static Operator DIVITION = new Operator ('/', 200 ){
Public BigDecimal eval (BigDecimal left, BigDecimal right ){
Return left. divide (right );
}
};

/**
* Merge operation
*/
Public final static Operator EXPONENT = new Operator ('^', 300 ){
Public BigDecimal eval (BigDecimal left, BigDecimal right ){
Return left. pow (right. intValue ());
}
};

Public char getOperator (){
Return operator;
}

Private void setOperator (char operator ){
This. operator = operator;
}

Public int getPriority (){
Return priority;
}

Private void setPriority (int priority ){
This. priority = priority;
}

/**
* Obtain the priority of an operator based on an operator.
*
* @ Param c
* @ Return operator priority
*/
Public static int getPrority (char c ){
Operator op = operators. get (c );
Return op! = Null? Op. getPriority (): 0;
}

/**
* Tool to determine whether a character is an operator
*
* @ Param c
* @ Return is an operator that returns true; otherwise, false is returned.
*/
Public static boolean isOperator (char c ){
Return getInstance (c )! = Null;
}

Public static boolean isOperator (String str ){
Return str. length ()> 1? False: isOperator (str. charAt (0 ));
}

/**
* Obtains the Operator instance based on the Operator.
*
* @ Param c
* @ Return the instance from the registered Operator. If the instance is not registered, null is returned.
*/
Public static Operator getInstance (char c ){
Return operators. get (c );
}

Public static Operator getInstance (String str ){
Return str. length ()> 1? Null: getInstance (str. charAt (0 ));
}

/**
* Calculate based on the operands
*
* @ Param left
* Left operand
* @ Param right
* Right operand
* @ Return Calculation Result
*/
Public abstract BigDecimal eval (BigDecimal left, BigDecimal right );

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.