Implementation effect

Implementation code:Github.com/winniecjy/f…

The demo:Winniecjy. Making. IO/formula – CAL…

Business background

The recent business involves a lot of data calculation. The database provides some basic data, and the final target data can be obtained based on these data and formula calculation. On the premise of a small amount of target data, simple and crude calculation can achieve the function. As the target data becomes more and more diverse, involving more and more basic data, problems arise:

  • The underlying data may come from different fields in different tables.
  • The formulas are just four basic operations but there are a lot of them, and they may change.

These problems result in longer, less readable code, and more costly subsequent modifications. If someone else wanted to get involved in this project, it would take a long time just to figure out exactly where to calculate the formula. This is easy to implement, but as the number of formulas increases and the number of fields used increases, the code becomes increasingly ugly. The final solution is to separate the formula as a configuration item for subsequent addition or modification. Perform operations based on a fixed format formula configuration to obtain the desired result.

Four operations are implemented

To evaluate the formula, you first need to implement the basic four operations (including the symbol ()+-*/) to parse a string of computations (e.g. ‘(7+5)/3-1’). The algorithm idea is as follows:

Step 1: Change the infix expression to the suffix expression

Infix expressions are more convenient for people to understand, while postfix expressions are more convenient for computer operation. So the infix expression is first converted to the suffix expression. This process requires an auxiliary stack that scans each character in the expression from left to right, doing the following:

  • Is a number directly appended to the end of a postfix expression
  • For the +-*/ operator, pops all secondary top of the stack elements with priority greater than or equal to that operator into the postfix expression, and then pushes that operator onto the stack
  • Is the left parenthesis (, directly pushed into the stack
  • Is the close parenthesis), pops the top of the stack element into the postfix expression until the first open parenthesis is matched, and the parenthesis is not printed to the postfix expression

When the traversal is complete, output the elements in the auxiliary stack, and you get the postfix expression. Note: The precedence of the operator is () < +- < */ Take A*(b-c)/D as an example. The processing is as follows:

| | current character postfix expression auxiliary stack | | | — – | — – | — – | — – | | A | A | | | * * | | A | | (| A | * (| | | AB | B * (| | – | AB | * (- | | C | ABC | *(- | | ) | ABC- | * | | / | ABC-* | / | | D | ABC-*D | / | | | ABC-*D/ | |

function infixToPostfix(infix, postfix) {
    let postfixHelper = Stack()
    for (let i=0; i<infix.length; i++) {
        let c = infix[i]
        if(! isOper(c)) {// Process operands
            let operands = ""
            while(i<infix.length && ! isOper(infix[i])) { operands += infix[i++] } postfix.push(operands) }else { // The processing operator does the processing
            handlerSymbol(c, postfixHelper, postfix)
        }
    }
    // If the input is finished, pop all the elements on the stack and add them to the suffix expression
    while(! postfixHelper.empty()) {let c = postfixHelper.top()
        postfix.push(c)
        postfixHelper.pop()
    }
}
Copy the code

Step 2: Evaluate postfix expressions

Calculating postfix expressions also requires an auxiliary stack, traversing characters from left to right and pushing operands into the auxiliary stack. When an operator is encountered, two elements are fetched from the top of the stack, the first being the right-hand operand and the second the left-hand operand (note that this order is result-dependent in division). The result of the operation is pushed onto the stack. Once the traversal is complete, the element at the top of the secondary stack is what you want.

function calcPostFix(postfix, data) {
  let helperStack = Stack()
  for (let i=0; i<postfix.length; i++) {
    let c = postfix[i]
    // If it is an operand, push it onto the stack
    if(! isOper(c)) {let op = formatData(c, data) // Get specific data according to formula rules
        helperStack.push(op)
    } else {
      // If it is the operator, pop the element from the stack for calculation
      let op1 = helperStack.top()
      helperStack.pop()
      let op2 = helperStack.top()
      helperStack.pop()
      if (op1 === null || op2 === null) {
        helperStack.push(null)}else {
        switch (c) {
          case "+":
            helperStack.push(op2 + op1)
            break
          case "-":
            helperStack.push(op2 - op1)
            break
          case "*":
            helperStack.push(op2 * op1)
            break
          case "/":
            helperStack.push(op2 / op1) Op2 (op)op1 instead of op1(op)op2
            break}}}}return helperStack.top()
}
Copy the code

Formula configuration rules

After four operations, we’re almost done with the main function just one more step away. Careful students will notice that when evaluating postfix expressions, formData is used to construct the data before the operands are pushed, and this is where our formula data matching is implemented. [table name]_[field name] is the fixed format, and the format of the input parameter data is:

constData = {[table name]: {[field1] :123[field,2] :456. }}Copy the code

With regular matching, you can get the table name and field name, and the correct operand from data.

conclusion

About extension

At present, this function is just a simple application in the background, which can ensure the legitimacy of the formula. This capability may later be exposed to business consumers who can customize the data and formulas they want. If there is a need later, this simple function needs to be packaged again to ensure the legitimacy of the formula, and at the same time to consider data security issues.

About the eval

With the current formula configuration principles, it is also possible to evaluate the string in eval directly in the db1.field1-db2.field2 format. It is not used for the following reasons:

  • Eval is a dangerous function. It is not recommended to use it.
  • The current calculation rules are relatively simple, because data tables are matched with the same key field to ensure that only one data is returned. If there are other changes in the future, the current form of re has better scalability.

AD time

Feizhu is bytedance’s office suite product, which deeply integrates functions such as instant communication, online collaboration, audio and video conferencing, calendar, cloud disk and workbench to provide users with one-stop collaboration experience. At present, feishu service customers have covered science and technology, Internet, information technology, manufacturing, construction and real estate, enterprise services, education, media and other fields. Welcome to the bytedance flying book team, there are a large number of front-end and back-end HC ~ scan the TWO-DIMENSIONAL code or click the link to send, look for the flying book team 👍 ~

Internal push code: HZNVPHS, delivery link:job.toutiao.com/s/JaeUCoc

[Recruitment] Delivery link:job.toutiao.com/s/JaevUNo