Evaluating a mathematical [removed]function) for a large number of input values fast

前端 未结 5 1614
你的背包
你的背包 2020-12-09 12:11

The following questions

  • Evaluating a mathematical expression in a string
  • Equation parsing in Python
  • Safe way to parse user-supplied
5条回答
  •  醉梦人生
    2020-12-09 12:37

    I'm not a Python coder, so I can't supply Python code. But I think I can provide a simple scheme that miminizes your dependencies and still runs pretty fast.

    The key here is to build something which is a close to eval without being eval. So what you want to do is "compile" the user equation into something which can be evaluated fast. OP has shown a number of solutions.

    Here is another based on evaluating the equation as Reverse Polish.

    For the sake of discussion, assume that you can convert the equation into RPN (reverse polish notation). This means operands come before operators, e.g., for the user formula:

            sqrt(x**2 + y**2)
    

    you get RPN equivalent reading left to right:

              x 2 ** y 2 ** + sqrt
    

    In fact, we can treat "operands", (e.g., variables and constants) as operators that take zero operands. Now everying in RPN is an operator.

    If we treat each operator element as a token (assume a unique small integer written as "RPNelement" below for each) and store them in an array "RPN", we can evaluate such a formula using a pushdown stack pretty fast:

           stack = {};  // make the stack empty
           do i=1,len(RPN),1
              case RPN[i]:
                  "0":  push(stack,0);
                  "1": push(stack,1);
                  "+":  push(stack,pop(stack)+pop(stack));break;
                   "-": push(stack,pop(stack)-pop(stack));break;
                   "**": push(stack,power(pop(stack),pop(stack)));break;
                   "x": push(stack,x);break;
                   "y": push(stack,y);break;
                   "K1": push(stack,K1);break;
                    ... // as many K1s as you have typical constants in a formula
               endcase
           enddo
           answer=pop(stack);
    

    You can inline the operations for push and pop to speed it up bit. If the supplied RPN is well formed, this code is perfectly safe.

    Now, how to get the RPN? Answer: build a little recursive descent parser, whose actions append RPN operators to the RPN array. See my SO answer for how to build a recursive descent parser easily for typical equations.

    You'll have to organize to put the constants encountered in parsing into K1, K2, ... if they are not special, commonly occuring values (as I have shown for "0" and "1"; you can add more if helpful).

    This solution should be a few hundred lines at most, and has zero dependencies on other packages.

    (Python experts: feel free to edit the code to make it Pythonesque).

提交回复
热议问题