Mapytex/pymath/calculus/expression.py

342 lines
11 KiB
Python
Raw Normal View History

2013-08-09 09:35:14 +00:00
#!/usr/bin/env python
# encoding: utf-8
2016-02-13 04:04:08 +00:00
# debuging
2016-03-06 15:18:01 +00:00
# from debug.tools import report
2015-04-21 15:31:56 +00:00
2016-03-06 15:18:01 +00:00
from .generic import flatten_list, expand_list, isOperator, isNumerand
from .str2tokens import str2tokens
2014-11-14 15:48:38 +00:00
from .operator import op
2016-02-15 12:20:24 +00:00
from .explicable import Explicable, Explicable_int, Explicable_Decimal
from .step import Step
2016-02-15 12:20:24 +00:00
from decimal import Decimal
2013-11-01 21:58:42 +00:00
from .random_expression import RdExpression
2015-02-27 16:46:16 +00:00
__all__ = ['Expression']
2013-08-09 09:35:14 +00:00
2016-03-06 15:18:01 +00:00
2015-02-27 16:46:16 +00:00
class Expression(Explicable):
"""A calculus expression. Today it can andle only expression with numbers later it will be able to manipulate unknown"""
2014-11-21 14:49:43 +00:00
@classmethod
2016-02-13 04:04:08 +00:00
def random(self, form="", conditions=[], val_min=-10, val_max=10):
2015-02-27 16:46:16 +00:00
"""Create a random expression from form and with conditions
:param form: the form of the expression (/!\ variables need to be in brackets {})
:param conditions: condition on variables (/!\ variables need to be in brackets {})
:param val_min: min value for generate variables
:param val_max: max value for generate variables
"""
random_generator = RdExpression(form, conditions)
return Expression(random_generator(val_min, val_max))
2013-08-09 11:08:24 +00:00
2016-03-09 07:50:25 +00:00
def __init__(self, exp):
"""Create Expression objects
2013-08-09 09:35:14 +00:00
:param exp: the expression. It can be a string or a list of postfix tokens.
2013-08-09 09:35:14 +00:00
"""
2015-06-21 18:43:38 +00:00
2016-02-13 04:04:08 +00:00
if isinstance(exp, str):
2016-03-09 07:50:25 +00:00
pstf_tokens = str2tokens(exp)
2015-06-21 18:43:38 +00:00
2016-02-13 04:04:08 +00:00
elif isinstance(exp, list):
2016-02-13 07:30:19 +00:00
# Ici on ne peut convertir les "+-*/..." en opérateur que s'ils sont
2016-02-13 04:04:08 +00:00
# d'arité 2.
exp_mod_op = [
2016-02-13 07:30:19 +00:00
op.get_op(i) if op.can_be_operator(i) else i for i in exp
]
2016-03-09 07:50:25 +00:00
pstf_tokens = flatten_list(
[tok.postfix_tokens if Expression.isExpression(tok)
else tok
for tok in exp_mod_op
]
2016-02-13 07:30:19 +00:00
)
2015-06-21 18:43:38 +00:00
2016-02-13 04:04:08 +00:00
elif isinstance(exp, Expression):
2016-03-09 07:50:25 +00:00
pstf_tokens = exp.postfix_tokens
2015-06-21 18:43:38 +00:00
elif isNumerand(exp):
2016-03-09 07:50:25 +00:00
pstf_tokens = [exp]
2015-06-21 18:43:38 +00:00
else:
2016-02-13 04:04:08 +00:00
raise ValueError(
"Can't build Expression with {} object".format(
2016-02-13 07:30:19 +00:00
type(exp)
)
)
2016-03-09 07:50:25 +00:00
super(Expression, self).__init__(pstf_tokens)
self.steal_history(exp)
self._isExpression = 1
2013-11-01 21:58:42 +00:00
def __str__(self):
"""
Overload str
If you want to changer render use Expression.set_render(...) or use tmp_render context manager.
"""
return self.STR_RENDER(self.postfix_tokens)
2014-11-21 14:49:43 +00:00
def simplify(self):
""" Compute entirely the expression and return the result with .steps attribute """
try:
self.compute_exp()
except ComputeError:
try:
self.simplified = self.postfix_tokens[0].simplify()
except AttributeError:
2016-03-06 15:18:01 +00:00
if isinstance(self.postfix_tokens[0], int):
self.simplified = Explicable_int(self.postfix_tokens[0])
2016-03-06 15:18:01 +00:00
elif isinstance(self.postfix_tokens[0], Decimal):
2016-02-15 12:20:24 +00:00
self.simplified = Explicable_Decimal(self.postfix_tokens[0])
else:
self.simplified = self
else:
self.simplified = self.child.simplify()
self.simplified.this_append_before(self.child.steps)
return self.simplified
2013-11-01 21:58:42 +00:00
def compute_exp(self):
""" Create self.child with and stock steps in it """
if len(self.postfix_tokens) == 1:
raise ComputeError("Nothing to compute in {}".format(self.postfix_tokens))
else:
ini_step = Step(self.postfix_tokens)
2013-11-01 21:58:42 +00:00
tokenList = self.postfix_tokens.copy()
tmpTokenList = []
2013-11-01 21:58:42 +00:00
while len(tokenList) > 2:
# on va chercher les motifs du genre A B +, quand l'operateur est
# d'arité 2, pour les calculer
if isNumerand(tokenList[0]) and isNumerand(tokenList[1]) \
and isOperator(tokenList[2]) and tokenList[2].arity == 2:
2016-02-13 03:29:26 +00:00
# S'il y a une opération à faire
op1 = tokenList[0]
op2 = tokenList[1]
operator = tokenList[2]
2016-02-13 03:29:26 +00:00
res = operator(op1, op2)
2013-11-01 21:58:42 +00:00
tmpTokenList.append(res)
2013-11-01 21:58:42 +00:00
# Comme on vient de faire le calcul, on peut détruire aussi les
# deux prochains termes
del tokenList[0:3]
# Et les motifs du gens A -, quand l'operateur est d'arité 1
elif isNumerand(tokenList[0]) \
and isOperator(tokenList[1]) and tokenList[1].arity == 1:
# S'il y a une opération à faire
op1 = tokenList[0]
operator = tokenList[1]
res = operator(op1)
tmpTokenList.append(res)
2016-02-13 03:29:26 +00:00
# Comme on vient de faire le calcul, on peut détruire aussi les
# deux prochains termes
del tokenList[0:2]
else:
tmpTokenList.append(tokenList[0])
del tokenList[0]
if len(tokenList) == 2 and isNumerand(tokenList[0]) \
and isOperator(tokenList[1]) and tokenList[1].arity == 1:
# S'il reste deux éléments dont un operation d'arité 1
op1 = tokenList[0]
operator = tokenList[1]
res = operator(op1)
tmpTokenList.append(res)
2016-02-13 04:04:08 +00:00
# Comme on vient de faire le calcul, on peut détruire aussi les
# deux prochains termes
del tokenList[0:2]
tmpTokenList += tokenList
self.child = Expression(tmpTokenList)
2016-03-07 08:51:57 +00:00
steps = Expression.develop_steps(tmpTokenList)
if self.child.postfix_tokens == ini_step.postfix_tokens:
self.child.steps = steps
else:
2016-03-02 14:00:55 +00:00
self.child.this_append_before([ini_step] + steps)
2016-03-07 08:51:57 +00:00
@classmethod
def develop_steps(cls, tokenList):
"""
From a list of tokens, it develops steps of each tokens and transpose it into steps respecting the stucture of the tokenList.
It try to use 'explain' method for every tokens. After using this methode, tokens becom amnesiac.
>>> e = Expression('1+2')
>>> e1 = e.simplify()
>>> f = Expression('3*4+5')
>>> f1 = f.simplify()
>>> Expression.develop_steps([e1, f1, op.add])
[< Step [1, 2, +, 3, 4, *, 5, +, +]>, < Step [3, 12, 5, +, +]>, < Step [3, 17, +]>]
>>> e = Expression('1+2')
>>> e1 = e.simplify()
>>> f = Expression('3*4+5')
>>> f1 = f.simplify()
>>> g = Expression('6+7')
>>> g1 = g.simplify()
>>> Expression.develop_steps([e1, f1, op.add, g1, op.mul])
[< Step [1, 2, +, 3, 4, *, 5, +, +, 6, 7, +, *]>, < Step [3, 12, 5, +, +, 13, *]>, < Step [3, 17, +, 13, *]>]
"""
with Step.tmp_render():
2016-03-09 07:50:25 +00:00
tmp_steps = list(Explicable.merge_history(tokenList))
2013-11-01 11:42:42 +00:00
steps = [Step(s) for s in tmp_steps]
return steps
2016-03-07 08:51:57 +00:00
@classmethod
2016-03-07 08:51:57 +00:00
def isExpression(cls, other):
try:
other._isExpression
except AttributeError:
2016-02-13 04:04:08 +00:00
return 0
return 1
# -----------
# Expression act as container from self.postfix_tokens
def __getitem__(self, index):
return self.postfix_tokens[index]
def __setitem__(self, index, value):
self.postfix_tokens[index] = value
# -----------
# Some math manipulations
2014-11-14 15:48:38 +00:00
def operate(self, other, operator):
2016-02-13 04:04:08 +00:00
if isinstance(other, Expression):
return Expression(
self.postfix_tokens +
other.postfix_tokens +
[operator])
elif isinstance(other, list):
2014-11-14 15:48:38 +00:00
return Expression(self.postfix_tokens + other + [operator])
else:
return Expression(self.postfix_tokens + [other] + [operator])
2016-02-13 03:29:26 +00:00
2014-11-14 15:48:38 +00:00
def roperate(self, other, operator):
2016-02-13 04:04:08 +00:00
if isinstance(other, Expression):
return Expression(
other.postfix_tokens +
self.postfix_tokens +
[operator])
elif isinstance(other, list):
2014-11-14 15:48:38 +00:00
return Expression(other + self.postfix_tokens + [operator])
else:
return Expression([other] + self.postfix_tokens + [operator])
def __add__(self, other):
2015-02-27 10:38:17 +00:00
""" Overload +
>>> a = Expression("1+2")
>>> print(a.postfix_tokens)
[1, 2, +]
2015-02-27 10:38:17 +00:00
>>> b = Expression("3+4")
>>> print(b.postfix_tokens)
[3, 4, +]
2015-02-27 10:38:17 +00:00
>>> c = a + b
>>> print(c.postfix_tokens)
[1, 2, +, 3, 4, +, +]
2015-02-27 10:38:17 +00:00
"""
2014-11-14 15:48:38 +00:00
return self.operate(other, op.add)
def __radd__(self, other):
return self.roperate(other, op.add)
def __sub__(self, other):
2015-02-27 10:38:17 +00:00
""" Overload -
>>> a = Expression("1+2")
>>> print(a.postfix_tokens)
[1, 2, +]
2015-02-27 10:38:17 +00:00
>>> b = Expression("3+4")
>>> print(b.postfix_tokens)
[3, 4, +]
2015-02-27 10:38:17 +00:00
>>> c = a - b
>>> print(c.postfix_tokens)
[1, 2, +, 3, 4, +, -]
2015-02-27 10:38:17 +00:00
"""
2014-11-14 15:48:38 +00:00
return self.operate(other, op.sub)
def __rsub__(self, other):
return self.roperate(other, op.sub)
2014-11-14 15:48:38 +00:00
def __mul__(self, other):
2015-02-27 10:38:17 +00:00
""" Overload *
>>> a = Expression("1+2")
>>> print(a.postfix_tokens)
[1, 2, +]
2015-02-27 10:38:17 +00:00
>>> b = Expression("3+4")
>>> print(b.postfix_tokens)
[3, 4, +]
2015-02-27 10:38:17 +00:00
>>> c = a * b
>>> print(c.postfix_tokens)
[1, 2, +, 3, 4, +, *]
2015-02-27 10:38:17 +00:00
"""
2014-11-14 15:48:38 +00:00
return self.operate(other, op.mul)
def __rmul__(self, other):
return self.roperate(other, op.mul)
2015-02-27 10:38:17 +00:00
def __truediv__(self, other):
""" Overload /
>>> a = Expression("1+2")
>>> print(a.postfix_tokens)
[1, 2, +]
2015-02-27 10:38:17 +00:00
>>> b = Expression("3+4")
>>> print(b.postfix_tokens)
[3, 4, +]
2015-02-27 10:38:17 +00:00
>>> c = a / b
>>> print(c.postfix_tokens)
[1, 2, +, 3, 4, +, /]
2016-02-13 03:29:26 +00:00
>>>
2015-02-27 10:38:17 +00:00
"""
2014-11-14 15:48:38 +00:00
return self.operate(other, op.div)
2015-02-27 15:56:43 +00:00
def __rtruediv__(self, other):
2014-11-14 15:48:38 +00:00
return self.roperate(other, op.div)
def __pow__(self, other):
2015-02-27 10:38:17 +00:00
return self.operate(other, op.pw)
def __xor__(self, other):
return self.operate(other, op.pw)
2014-11-14 15:48:38 +00:00
def __neg__(self):
return Expression(self.postfix_tokens + [op.sub1])
2016-02-13 03:29:26 +00:00
class ExpressionError(Exception):
pass
2016-03-06 15:18:01 +00:00
class ComputeError(Exception):
pass
2013-11-01 11:42:42 +00:00
2013-08-09 09:35:14 +00:00
# -----------------------------
# Reglages pour 'vim'
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
2016-02-13 03:29:26 +00:00
# cursor: 16 del