Coder Social home page Coder Social logo

py-expression-eval's Introduction

Python Mathematical Expression Evaluator

Build Status

PyPi version PyPi downloads

Coverage Status

Based on js-expression-eval, by Matthew Crumley ([email protected], http://silentmatt.com/) https://github.com/silentmatt/js-expression-eval

Ported to Python and modified by @cansadadeserfeliz.

You are free to use and modify this code in anyway you find useful. Please leave this comment in the code to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code, but don't feel like you have to let me know or ask permission.

Installation

pip install py_expression_eval

Tests

python setup.py test

Documentation

All the classes and methods of py-expression-eval were written as similar as possible to their analogues from js-expression-eval to make it easier to use for validation on back-end side.

Parser

Parser is the main class of the library that contains the methods to parse, evaluate and simplify mathematical expressions. In order to use the library you need to create an instance of this class:

> from py_expression_eval import Parser
> parser = Parser()

Once you instantiated Parser class, you can create Expression object using parse method:

> parser.parse('2 * 3')
Out: <py_expression_eval.Expression instance at 0x7f40cc4e5ef0>

Parser.Expression

evaluate() takes a dictionary with variables as a parameter and returns the value of the expression:

> parser.parse('2 * 3').evaluate({})
Out: 6
> parser.parse('2 * 3.0').evaluate({})
Out: 6.0
> parser.parse('2 * x').evaluate({'x': 7})
Out: 14
> parser.parse('2 * x').evaluate({'x': 7.0})
Out: 14.0

substitute() creates a new expression where specified variables are replaces with a new expression. For example, to replace x with 3 + x in 2 * x expression we use the following code:

> parser.parse('2 * x').substitute('x', '3 + x').toString()
Out: '(2*(3+x))'

variables() returns a list of the variables for the expression:

> parser.parse('2 * x + y').variables()
Out: ['x', 'y']

simplify() simplifies the expression. For example,

> parser.parse('2 * 3 * x + y').simplify({}).toString()
Out: '((6*x)+y)'
> parser.parse('2 * 3 * x + y').simplify({'x': -1}).toString()
Out: '(-6+y)'
> parser.parse('cos(PI) + x').simplify({}).toString()
Out: '(-1.0+x)'

toString() converts the expression to a string.

Available operators, constants and functions

Expression Example Output
+ parser.parse('2 + 2').evaluate({}) 4
- parser.parse('3 - 1').evaluate({}) 2
* parser.parse('2 * 3').evaluate({}) 6
/ parser.parse('5 / 2').evaluate({}) 2.5
% parser.parse('5 % 2').evaluate({}) 1
^ parser.parse('5 ^ 2').evaluate({}) 25.0
PI parser.parse('PI').evaluate({}) 3.141592653589793
E parser.parse('E').evaluate({}) 2.718281828459045
sin(x) parser.parse('sin(0)').evaluate({}) 0.0
cos(x) parser.parse('cos(PI)').evaluate({}) - 1.0
tan(x) parser.parse('tan(0)').evaluate({}) 0.0
asin(x) parser.parse('asin(0)').evaluate({}) 0.0
acos(x) parser.parse('acos(-1)').evaluate({}) 3.141592653589793
atan(x) parser.parse('atan(PI)').evaluate({}) 1.2626272556789118
log(x) parser.parse('log(1)').evaluate({}) 0.0
log(x, base) parser.parse('log(16, 2)').evaluate({}) 4.0
abs(x) parser.parse('abs(-1)').evaluate({}) 1
ceil(x) parser.parse('ceil(2.7)').evaluate({}) 3.0
floor(x) parser.parse('floor(2.7)').evaluate({}) 2.0
round(x) parser.parse('round(2.7)').evaluate({}) 3.0
exp(x) parser.parse('exp(2)').evaluate({}) 7.38905609893065
and parser.parse('a and b').evaluate({'a':True, 'b':True}) True
or parser.parse('a or b').evaluate({'a':True, 'b':True}) True
xor parser.parse('a xor b').evaluate({'a':True, 'b':True}) False
not parser.parse('a and not b').evaluate({'a':True, 'b':True}) False
in parser.parse('1 in (1,2,3)').evaluate({}) True

Examples

from py_expression_eval import Parser

parser = Parser()
parser.parse('2 * 3').evaluate({})  # 6
parser.parse('2 ^ x').evaluate({'x': 3})  # 8.0
parser.parse('2 * x + 1').evaluate({'x': 3})  # 7
parser.parse('2 + 3 * x').evaluate({'x': 4})  # 14
parser.parse('(2 + 3) * x').evaluate({'x': 4}) # 20
parser.parse('2-3^x').evaluate({'x': 4})  # -79.0
parser.parse('-2-3^x').evaluate({'x': 4})  # -83.0
parser.parse('-3^x').evaluate({'x': 4})  # -81.0
parser.parse('(-3)^x').evaluate({'x': 4})  # 81.0
parser.parse('2*x + y').evaluate({'x': 4, 'y': 1})  # 9
parser.parse('round(log(2.7))').evaluate({}) # 1.0

# substitute
expr = parser.parse('2 * x + 1')
expr2 = expr.substitute('x', '4 * x')  # ((2*(4*x))+1)
expr2.evaluate({'x': 3})  # 25

# simplify
expr = parser.parse('x * (y * atan(1))').simplify({'y': 4})
expr.toString()  # x*3.141592
expr.evaluate({'x': 2})  # 6.283185307179586

# get variables
expr = parser.parse('x * (y * atan(1))')
expr.variables()  # ['x', 'y']
expr.simplify({'y': 4}).variables()  # ['x']

Available operations

from py_expression_eval import Parser

parser = Parser()
parser.parse('2 + 3').evaluate({})  # 5
parser.parse('2 - 3').evaluate({})  # -1
parser.parse('2 * 3').evaluate({})  # 6
parser.parse('2 / 3').evaluate({})  # 0.6666666666666666
parser.parse('2 % 3').evaluate({})  # 2
parser.parse('-2').evaluate({})  # -2
parser.parse('abs(-2)').evaluate({}) # 2

parser.parse('ceil(1.4)').evaluate({})  # 2.0
parser.parse('floor(1.4)').evaluate({})  # 1.0
parser.parse('round(1.4)').evaluate({})  # 1.0

parser.parse('2^3').evaluate({})  # 8.0
parser.parse('sqrt(16)').evaluate({}) # 4.0

parser.parse('sin(3.14)').evaluate({})  # 0.0015926529164868282
parser.parse('cos(3.14)').evaluate({})  # -0.9999987317275395
parser.parse('tan(3.14)').evaluate({})  # -0.0015926549364072232

parser.parse('asin(1)').evaluate({})  # 1.5707963267948966
parser.parse('acos(1)').evaluate({})  # 0.0
parser.parse('atan(1)').evaluate({})  # 0.7853981633974483

parser.parse('log(2.7)').evaluate({})  # 0.9932517730102834
parser.parse('exp(1)').evaluate({})  # 2.718281828459045

parser.parse('log(E)').evaluate({})  # 1.0
parser.parse('cos(PI)').evaluate({})  # -1.0

parser.parse('x||y').evaluate({'x': 2, 'y': 3})  # '23'

parser.parse('num in (1,2,3)').evaluate({'num': 1})  # True
parser.parse('"word" in "word in sentence"').evaluate({})  # True

Upload package to PyPi

Generating distribution archives

python3 setup.py sdist bdist_wheel

Upload distribution

ls -a dist/
twine upload dist/py_expression_eval-0.3.9*

Check on: https://pypi.org/project/py-expression-eval/0.3.9/

More details: https://packaging.python.org/tutorials/packaging-projects/

py-expression-eval's People

Contributors

baguage avatar camilonova avatar cansadadeserfeliz avatar danysirota avatar epicfaace avatar flotwig avatar gmiller-in-situ avatar herrfrei avatar hyperkang avatar jeremyeudy avatar joachimvalente avatar kurtjacobson avatar laurence-hudson-mindfoundry avatar mbcheikh avatar octaviancorlade avatar pablovallejo avatar parraman avatar sbozzolo avatar tahme avatar thimel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

py-expression-eval's Issues

Zero-argument functions don't work

def f():
    return 1
parser.evaluate('f()', variables={'f': f})

Assuming PR 20 is accepted, this is the output:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "py_expression_eval/__init__.py", line 494, in evaluate
    return self.parse(expr).evaluate(variables)
  File "py_expression_eval/__init__.py", line 140, in evaluate
    nstack.append(f(n1))
TypeError: f() takes 0 positional arguments but 1 was given

This seems to be because n1 = nstack.pop() results in n1 = 0, even though there should be no arguments.

Without PR 20 it still fails, but in a different way.

sign directly after * cause crash

This expression perse without problem
parser.parse('-2*3').evaluate({})

This expression crash
parser.parse('2*-3').evaluate({})


_IndexError Traceback (most recent call last)
in
----> 1 parser.parse('2*-3').evaluate({})

~/Library/Python/3.7/lib/python/site-packages/py_expression_eval/init.py in evaluate(self, values)
118 elif type_ == TOP2:
119 n2 = nstack.pop()
--> 120 n1 = nstack.pop()
121 f = self.ops2[item.index_]
122 nstack.append(f(n1, n2))

IndexError: pop from empty list_

eval('2*-3') works on python3.7

Conversion issue when evaluating some expressions

Consider the following expression:

Parser().parse("x/((x+y))").simplify({}).evaluate({'x':1, 'y':1}) 
# this gives 0 as integer instead of 0.5

The "workaround" seems to be

Parser().parse("x*1/((x+y))").simplify({}).evaluate({'x':1, 'y':1})  
# this gives 0.5.

The evaluate function somewhere returns a integer 0 and drops the decimal place. Should there be an additional optional argument to return the result with desired precision?

Support comparison chaining

In Python, the following syntax is valid:

>>> x = 7
>>> 5 < x < 10
True
>>> x = 1
>>> 5 < x < 10
False

However, this does not work in py-expression-eval:

>>> from py_expression_eval import Parser
>>> parser = Parser()
>>> parser.parse('5 < x < 10').evaluate({'x': 7})
True
>>> parser.parse('5 < x < 10').evaluate({'x': 1})
True

The last result is unexpected; the parser unconditionally returns True when chaining comparison.

Support date comparison in parser.

I calling following user defined functions from parser.
def conditional_res(formula,res1,res2): res = pd.Series() res = np.where(formula,res1,res2) return res df['check']=parser.parse("conditional_res(Col1==Col2,'Yes','No')").evaluate(dct)
Above code works fine with String and Number types but date type. Is there a way to add support for Date comparison?

fac() not working

parser.parse("fac(5)").evaluate({})
Traceback (most recent call last):
File "<pyshell#48>", line 1, in
parser.parse("fac()").evaluate({})
File "\py_expression_eval_init_.py", line 141, in evaluate
nstack.append(call(f, n1))
NameError: name 'call' is not defined

Complex numbers?

This module doesn't seem to support complex numbers. Is there a way to handle complex numbers within this module?

power operator '**' not working

Below codes are expected to return val as 8.0

from py_expression_eval import Parser
parser = Parser()
val = parser.parse('x**3').evaluate({'x': 2})

However it ran into error as follows:

Traceback (most recent call last):
  File "/home/kang/apps/pycharm-community-2018.1.1/helpers/pydev/pydevd.py", line 1664, in <module>
    main()
  File "/home/kang/apps/pycharm-community-2018.1.1/helpers/pydev/pydevd.py", line 1658, in main
    globals = debugger.run(setup['file'], None, None, is_module)
  File "/home/kang/apps/pycharm-community-2018.1.1/helpers/pydev/pydevd.py", line 1068, in run
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/home/kang/gh-hyperkang/pylib/pyfem-ansys/pyfem_ansys/cdb/ansys_utils.py", line 154, in <module>
    print parser.parse('x**3').evaluate({'x': 2})
  File "/home/kang/gh-hyperkang/pylib/py-expression-eval/py_expression_eval/__init__.py", line 493, in parse
    self.error_parsing(self.pos, 'parity')
  File "/home/kang/gh-hyperkang/pylib/py-expression-eval/py_expression_eval/__init__.py", line 503, in error_parsing
    raise Exception(self.errormsg)
Exception: parse error [column 4]: parity

Min/max for single integer.

It is not possible to calculate min(x) or max(x) with a single integer. It is required to wrap it in tuple:
Input: parser.parse('min(x)').evaluate({"x": 3}) or parser.parse('min(3)').evaluate({})
Result: TypeError: 'int' object is not iterable

Despite this number can be parsed:
Input: parser.parse('min(x)') or parser.parse('min(3)')
Result: <py_expression_eval.Expression object at 0x7fd984ad8f10>

(v0.3.1) tests are not passing on python 2.7.10

Just noticed this problem when running tests:

Error

Traceback (most recent call last):
  File "D:\Repositories\py-expression-eval-baguage\py_expression_eval\tests.py", line 43, in test_parser
    self.assertEqual(parser.parse('pyt(2 , 0)').evaluate({}),2)
  File "D:\Repositories\py-expression-eval-baguage\py_expression_eval\__init__.py", line 138, in evaluate
    nstack.append(apply(f, n1))
NameError: name 'apply' is not defined

No documentation

There seems to be absolutely no documentation.

There isn't even a description.

The user can't figure out what module name to import in Python.

parser.py

parser.py should be renamed, because there is parser module in Python, so from parser import Parser raises «ImportError: cannot import name Parser».

Also, as I understand, nested Expression class is never used and may be removed.

No way to pass mix of scalars and lists to function

Lists are flattened, so there's no way to pass in an entire list as an argument to a function.

def f(xs, y):
     return sum(x + y for x in xs)

f([1,2,3], 2)
# 12

parser.evaluate("f(xs, y)", variables={"f": f, "xs": [1,2,3], "y": 2})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "py_expression_eval/__init__.py", line 494, in evaluate
    return self.parse(expr).evaluate(variables)
  File "py_expression_eval/__init__.py", line 138, in evaluate
    nstack.append(f(*n1))
TypeError: f() takes 2 positional arguments but 4 were given

how to convert this code in python lang.

arr1 = [{
"phone": "123",
"status": "ON",
"id": "id1"
}, {
"phone": "1234",
"status": "ON",
"id": "id1"
}, {
"phone": "1235",
"status": "ON",
"id": "id1"
}]

arr2 = [{
"phone": 123,
"status": "ON",
"id": "id1"
}, {
"phone": "1234",
"status": "ON",
"id": "id1"
}, {
"phone": "1235",
"status": "ON",
"id": "id1"
}]

expression = "$arr1_phone == $arr2_phone";

example

dic = {
"arr1_phone": "123",
"arr1_stratus": "ON",
"arr1_id": "id1",
"arr2_phone": "123",
"arr2_stratus": "ON",
"arr2_id": "id1",
}

finalArray = [];
for (int i=0; i<arr1.length; i++){
for (int j=0; j< arr2.length; j++){
keys = arr1.keys();
dictionary;
for (k=0;k< keys.length;k++){
dictionary["arr1_"+keys[k]] = arr1[i][keys[k]]
}

var keys2 = arr2.keys();
for (k=0;k< keys.length;k++){
dictionary["arr2_"+keys2[k]] = arr1[i][keys2[k]]
}

var result = parser.parse(expression).evaluate(dictionary)

if (result) {
finalArray.push(arr1);
}
}
}

Request to add logical "not"

I'm using this package in a significant project where all my expressions are logical expressions.
While the AND and OR operators are implemented and work well, the NOT operator is not.

Would be wonderful if the author could add this feature.

Thanks

Error in parsing '1E3*x' and '1e3*x'

>>> from py_expression_eval import Parser
>>> parser = Parser()
>>> parser.parse('1E3*x')
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "/home/kang/Envs/simright_cpython_2.7.13/lib/python2.7/site-packages/py_expression_eval/__init__.py", line 610, in parse
    self.error_parsing(self.pos, 'unexpected variable')
  File "/home/kang/Envs/simright_cpython_2.7.13/lib/python2.7/site-packages/py_expression_eval/__init__.py", line 639, in error_parsing
    raise Exception(self.errormsg)
Exception: parse error [column 3]: unexpected variable

>>> parser.parse('1e3*x')
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "/home/kang/Envs/simright_cpython_2.7.13/lib/python2.7/site-packages/py_expression_eval/__init__.py", line 610, in parse
    self.error_parsing(self.pos, 'unexpected variable')
  File "/home/kang/Envs/simright_cpython_2.7.13/lib/python2.7/site-packages/py_expression_eval/__init__.py", line 639, in error_parsing
    raise Exception(self.errormsg)
Exception: parse error [column 3]: unexpected variable

>>> parser.parse('1000*x')
<py_expression_eval.Expression instance at 0x7f72039d4998>

As both 1e3 and 1E3 are valid numbers in Python, they shall be allowed to be used in expression.

Btw, I noticed that E and PI are defined as constants in py-expression-eval. Do we have options to disable such implication? As E and PI may appear in expressions as normal variable names, and they are both valid variable names in Python.

Using value from dictionary in expression

Is there a way I can use dictionary value in my expression? For e.g -
I have a dictionary data={numa: 8,numb:3,numc5}

I am trying todo something like data.numa+data.numb-data.numc.

Added support for integers in hexadecimal notation

Hi!

First of all, thank you for your work and your useful package :-)

While using it, I have stumbled with the need of parsing hexadecimal numbers. I have made the modifications to the original code myself, as well as a set of unit tests. These modifications are part of pull request #73.

I hope you find it right and useful and, if you consider it necessary, add this feature to the package.

Thank you very much in advance.

Adding custom operators(tokens)

Hi vero4karu.

I recently started using your module in a project of mine. Thanks for the module, it saved me a lot of time! There is one important feature that was missing for me, I will describe it as an Feature Request.

Feature

Mechanism to add custom operators (token), that are included when parsing an expression.

Expected Behaviour

 parser = Parser() 
 parser.regiser_operation("&", 2, "&", foo)

Description

The function register_operation in this case has the signature

parser.register_operation(token, priority, index, func)

where (token, priority, index) would be equivalent to the definition in isOperator():
https://github.com/Axiacore/py-expression-eval/blob/8578be5e403904e0612eb725f02c53a60ac37d1e/py_expression_eval/__init__.py#L650-L670

and func a custom function that shall be executed.

Solution proposal

I already forked your repo and implemented this feature. I already tested this feature in my project and I'm happy with it. Especially if you use overloading of Operators you can use the evaluation for Custom classes (Or numpy arrays). And that is totally awsome.
If you think, that this feature make sense, I`d gladly start a PR, where we can discuss the details.

Best wishes
Mike

Not all alphanumeric variables possible

Structural engineers use special alphanumeric variables for principal strains. These are called: e1 and e2 (usually an Epsilon, but an "e" is taken instead).
But those alphanumeric variables that start with "e" are not possible with py-expression-eval because it conflicts with the identification of numbers in scientific notation. This is exactly what happens in this line.

There are 2 solutions. The first is quite simple and can be solved with the statement above the line. Here an example:

# Return if Euler's number or if starting with 'e' which could be an alphanumeric variable like e1, e2...
if self.expression[self.pos] == 'E' or self.expression[self.pos] == 'e':
    return False

That would be OK because a scientific number that started with nothing before the "e" wouldn't be a scientific number, and hardly anyone would do that.

The second solution would be to adjust the RE pattern so that at least a single digit number before the "e" is mandatory.

Can you please consider this in the next versions?

Thanks and best regards from Hamburg :)

"IN" operator

An "in" operator will be very useful.

Now instead of doing num in [1,4,7,9,12] we need to do num == 1 or num == 4 or num == 7....

What's the difference between expression.functions and expression.values?

Hey there!

I'm trying to add new functions, but I can't seem to understand the difference between expression.functions and expression.values. Some functions (such as the trigonometric ones) are both in expression.functions and in expression.values. And some of them are even in ops1.

Why is that? Should I add new functions in exp.ops1, exp.functions and exp.values, or just in exp.functions? Does it depend on the function?

Thanks in advance.

toString() it's not working with negative numbers

Hi my friend!

I was working perfectly with your library until I had a trouble with a simple example:
When I was trying to parse '-1' I do:

>>> parser.parse('-1').toString()

Shows an error:
Traceback (most recent call last):
File "", line 1, in
File "/Library/Python/2.7/site-packages/py_expression_eval/init.py", line 185, in toString
nstack.append('(' + f + n1 + ')')
TypeError: cannot concatenate 'str' and 'float' objects

Thanks for read my issue and have good code lines.

Substitute with scientific notation

The following lines:

p = Parser()
x = 0.00000002
p.parse('2*x').substitute('x', x)

Give this error:
File "/usr/local/lib/python3.5/dist-packages/py_expression_eval/init.py", line 87, in substitute
expr = Parser().parse(str(expr))
File "/usr/local/lib/python3.5/dist-packages/py_expression_eval/init.py", line 610, in parse
self.error_parsing(self.pos, 'unexpected variable')
File "/usr/local/lib/python3.5/dist-packages/py_expression_eval/init.py", line 639, in error_parsing
raise Exception(self.errormsg)
Exception: parse error [column 2]: unexpected variable

python - py-expression-eval - Passing a numpy ndarray

I am using py-expression-eval library for evaluating an expression.

from py-expression-eval import Parser
parser=Parser()
a = np.array([1,2,3])
parser.parse('x*5').evaluate({'x':a})
#Works and outputs
array([  5.,  10.,  15.])

However

parser.parse('sin(x)').evaluate({'x':a})
#Throws a error
Traceback (most recent call last):
File "<pyshell#52>", line 1, in <module>
parser.parse('sin(x)').evaluate({'x':a})
File "C:\Python27\lib\site-packages\py_expression_eval\__init__.py", line 133, in evaluate
nstack.append(f(n1))
TypeError: only length-1 arrays can be converted to Python scalars

But if I pass values of array one by one it works

parser.parse('sin(x)').evaluate({'x':a[0]})
0.8414709848078965

StackOverflow link

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.