Mapytex/mapytex/calculus/expression.py

339 lines
10 KiB
Python

#!/usr/bin/env python
# encoding: utf-8
# debuging
# from debug.tools import report
from .generic import flatten_list, expand_list, isOperator, isNumerand
from .str2tokens import str2tokens
from .operator import op
from .explicable import Explicable, Explicable_int, Explicable_Decimal
from .step import Step
from decimal import Decimal
from .random_expression import RdExpression
__all__ = ['Expression']
class Expression(Explicable):
"""A calculus expression. Today it can andle only expression with numbers later it will be able to manipulate unknown"""
@classmethod
def random(self, form="", conditions=[], val_min=-10, val_max=10):
"""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))
def __init__(self, exp):
"""Create Expression objects
:param exp: the expression. It can be a string or a list of postfix tokens.
"""
if isinstance(exp, str):
pstf_tokens = str2tokens(exp)
elif isinstance(exp, list):
# Ici on ne peut convertir les "+-*/..." en opérateur que s'ils sont
# d'arité 2.
exp_mod_op = [
op.get_op(i) if op.can_be_operator(i) else i for i in exp
]
pstf_tokens = flatten_list(
[tok.postfix_tokens if Expression.isExpression(tok)
else tok
for tok in exp_mod_op
]
)
elif isinstance(exp, Expression):
pstf_tokens = exp.postfix_tokens
elif isNumerand(exp):
pstf_tokens = [exp]
else:
raise ValueError(
"Can't build Expression with {} object".format(
type(exp)
)
)
super(Expression, self).__init__(pstf_tokens)
self._isExpression = 1
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:
if isinstance(self.postfix_tokens[0], int):
self.simplified = Explicable_int(self.postfix_tokens[0])
elif isinstance(self.postfix_tokens[0], Decimal):
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
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)
tokenList = self.postfix_tokens.copy()
tmpTokenList = []
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:
# S'il y a une opération à faire
op1 = tokenList[0]
op2 = tokenList[1]
operator = tokenList[2]
res = operator(op1, op2)
tmpTokenList.append(res)
# 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)
# 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)
# 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)
steps = Expression.develop_steps(tmpTokenList)
if self.child.postfix_tokens == ini_step.postfix_tokens:
self.child.steps = steps
else:
self.child.this_append_before([ini_step] + steps)
@classmethod
def develop_steps(cls, tokenList):
r"""
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()
>>> dev_steps = Expression.develop_steps([e1, f1, op.add])
>>> dev_steps
[< Step [1, 2, +, 3, 4, *, 5, +, +]>, < Step [3, 12, 5, +, +]>, < Step [3, 17, +]>]
>>> for i in dev_steps:
... print(i)
1 + 2 + 3 \times 4 + 5
3 + 12 + 5
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():
tmp_steps = list(Explicable.merge_history(tokenList))
steps = [Step(s) for s in tmp_steps]
return steps
@classmethod
def isExpression(cls, other):
try:
other._isExpression
except AttributeError:
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
def operate(self, other, operator):
if isinstance(other, Expression):
return Expression(
self.postfix_tokens +
other.postfix_tokens +
[operator])
elif isinstance(other, list):
return Expression(self.postfix_tokens + other + [operator])
else:
return Expression(self.postfix_tokens + [other] + [operator])
def roperate(self, other, operator):
if isinstance(other, Expression):
return Expression(
other.postfix_tokens +
self.postfix_tokens +
[operator])
elif isinstance(other, list):
return Expression(other + self.postfix_tokens + [operator])
else:
return Expression([other] + self.postfix_tokens + [operator])
def __add__(self, other):
""" Overload +
>>> a = Expression("1+2")
>>> print(a.postfix_tokens)
[1, 2, +]
>>> b = Expression("3+4")
>>> print(b.postfix_tokens)
[3, 4, +]
>>> c = a + b
>>> print(c.postfix_tokens)
[1, 2, +, 3, 4, +, +]
"""
return self.operate(other, op.add)
def __radd__(self, other):
return self.roperate(other, op.add)
def __sub__(self, other):
""" Overload -
>>> a = Expression("1+2")
>>> print(a.postfix_tokens)
[1, 2, +]
>>> b = Expression("3+4")
>>> print(b.postfix_tokens)
[3, 4, +]
>>> c = a - b
>>> print(c.postfix_tokens)
[1, 2, +, 3, 4, +, -]
"""
return self.operate(other, op.sub)
def __rsub__(self, other):
return self.roperate(other, op.sub)
def __mul__(self, other):
""" Overload *
>>> a = Expression("1+2")
>>> print(a.postfix_tokens)
[1, 2, +]
>>> b = Expression("3+4")
>>> print(b.postfix_tokens)
[3, 4, +]
>>> c = a * b
>>> print(c.postfix_tokens)
[1, 2, +, 3, 4, +, *]
"""
return self.operate(other, op.mul)
def __rmul__(self, other):
return self.roperate(other, op.mul)
def __truediv__(self, other):
""" Overload /
>>> a = Expression("1+2")
>>> print(a.postfix_tokens)
[1, 2, +]
>>> b = Expression("3+4")
>>> print(b.postfix_tokens)
[3, 4, +]
>>> c = a / b
>>> print(c.postfix_tokens)
[1, 2, +, 3, 4, +, /]
>>>
"""
return self.operate(other, op.div)
def __rtruediv__(self, other):
return self.roperate(other, op.div)
def __pow__(self, other):
return self.operate(other, op.pw)
def __xor__(self, other):
return self.operate(other, op.pw)
def __neg__(self):
return Expression(self.postfix_tokens + [op.sub1])
class ExpressionError(Exception):
pass
class ComputeError(Exception):
pass
# -----------------------------
# Reglages pour 'vim'
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
# cursor: 16 del