HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Performance of parsing math functions

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
functionsmathparsingperformance

Problem

I wrote the following code as part of a larger application. This code takes a string that defines a mathematical function on such as r,y x[0], x[1] etc. or any other variable that additionally can be subscripted (indicated by the x[0] notation) and returns valid python function that accepts the the arguments and computes the final value.

For instance, the string "x+y" gets converted to lambda x,y: x+y.

Since the larger application will be calling the returned function repeatedly I would like to optimise the calling of the returned function. (The overhead for converting the function isn't much of any issue). That is when I noticed a disparity: the equivalent function hard coded into python is over 45 times faster!

Is there a way to reduce the time it takes to call the returned function?

Aside from the performance reason for posting this I would appreciate some constructive criticism regarding the quality and clarity of my code. I am aware that the current version doesn't indicate syntax errors in the entered expression, however I am working on the assumption that some exception will be thrown if that is the case. If this doesn't work in some cases I've missed please let me know.

```
import math

class Parser:
"""A recursive parser of mathematical expressions that
supports variable and subscripted variables"""

#Defines the binary operators that can be used in the expressions
operators=[['+', lambda x, y: x+y],
['-', lambda x, y: x-y],
['', lambda x, y: xy],
['/', lambda x, y: x/y],
['^', lambda x, y: x**y]]
#Defines the functions that can be used in the expressions
functions={'sin':math.sin,
'cos':math.cos,
'tan':math.tan,
'asin':math.asin,
'acos':math.acos,
'atan':math.atan,
'sqrt':math.sqrt,
'abs':math.fabs,
'floor':math.floor,

Solution

While I'm still unsatisfied with the code I managed to take it from 45 times slower to 15 time slower then hard coding.

def travel(self):
    "Returns a python function that reflect the tree of self"

    #if function type is a simple number return a function that returns it
    if(type(self.function)==type(1.0) or type(self.function)==type(1)):
        def actual_function(**kargs):
            return self.function

    elif(type(self.function)==type(' ')):

        #if the function contains [ and ] assume its a subscripted variable
        if('[' in self.function and ']' in self.function):
            var_name=self.function.split('[')[0]
            var_index=int(self.function.split('[',1)[1][:-1])
            def actual_function(**kargs):
                #the variables index "picked" from the list in the user arguments
                return kargs[var_name][var_index]

        else:
            #other wise return a function that returns its variable "picked" from the user arguments
            def actual_function(**kargs):
                return kargs[self.function]

    else:
        #return a function that returns the value of this nodes function using the leaves as arguments
        function_list=list(map(lambda x: x.travel(), self.leaves))
        def actual_function(**kargs):
            return self.function(* [x(**kargs) for x in function_list] )

        # def actual_function(**kargs):
        #     return self.function(* list(map(lambda x: x.travel()(**kargs), self.leaves)) )

    #return the function that we determined in the previous step
    return actual_function


The key lies in redefining travel such that as much as possible is calculated outside actual_function.

Code Snippets

def travel(self):
    "Returns a python function that reflect the tree of self"

    #if function type is a simple number return a function that returns it
    if(type(self.function)==type(1.0) or type(self.function)==type(1)):
        def actual_function(**kargs):
            return self.function

    elif(type(self.function)==type(' ')):

        #if the function contains [ and ] assume its a subscripted variable
        if('[' in self.function and ']' in self.function):
            var_name=self.function.split('[')[0]
            var_index=int(self.function.split('[',1)[1][:-1])
            def actual_function(**kargs):
                #the variables index "picked" from the list in the user arguments
                return kargs[var_name][var_index]

        else:
            #other wise return a function that returns its variable "picked" from the user arguments
            def actual_function(**kargs):
                return kargs[self.function]

    else:
        #return a function that returns the value of this nodes function using the leaves as arguments
        function_list=list(map(lambda x: x.travel(), self.leaves))
        def actual_function(**kargs):
            return self.function(* [x(**kargs) for x in function_list] )

        # def actual_function(**kargs):
        #     return self.function(* list(map(lambda x: x.travel()(**kargs), self.leaves)) )

    #return the function that we determined in the previous step
    return actual_function

Context

StackExchange Code Review Q#83596, answer score: 4

Revisions (0)

No revisions yet.