patternpythonMinor
Performance of parsing math functions
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
For instance, the string "x+y" gets converted to
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,
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.
The key lies in redefining travel such that as much as possible is calculated outside actual_function.
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_functionThe 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_functionContext
StackExchange Code Review Q#83596, answer score: 4
Revisions (0)
No revisions yet.