#!/usr/bin/env python # encoding: utf-8 # debuging #from debug.tools import report from .generic import Stack, flatten_list, expand_list, isNumber, isOperator, isNumerand from .str2tokens import str2tokens from .operator import op from .explicable import Explicable, Explicable_int, Explicable_Decimal from decimal import Decimal from .random_expression import RdExpression __all__ = ['Expression'] def pstf_factory(pstf_tokens): """Factory which tranform postfix tokens list into an Expression or the simpliest object type ready to be rendered :param pstf_tokens: a postfix tokens list :returns: the object >>> from .operator import op >>> pstf_t = [2, 3, op.add] >>> pstf_factory(pstf_t) < [2, 3, +] > >>> pstf_factory([2]) 2 >>> type(pstf_factory([2])) >>> pstf_factory([2.45]) Decimal('2.45') >>> type(pstf_factory([2.45])) >>> from .fraction import Fraction >>> f = Fraction(1,2) >>> pstf_factory([f]) < Fraction 1 / 2> """ try: l_pstf_token = len(pstf_tokens) except TypeError: if isinstance(pstf_tokens[0], int): return Explicable_int(pstf_tokens[0]) elif isinstance(pstf_tokens[0], Decimal): return Explicable_Decimal(pstf_tokens[0]) elif isinstance(pstf_tokens[0], float): return Explicable_Decimal(Decimal(str(pstf_tokens[0]))) elif hasattr(pstf_tokens[0], 'STR_RENDER'): return pstf_tokens[0] else: return Expression(self) else: if l_pstf_token == 1: if isinstance(pstf_tokens[0], int): return Explicable_int(pstf_tokens[0]) elif isinstance(pstf_tokens[0], Decimal): return Explicable_Decimal(pstf_tokens[0]) elif isinstance(pstf_tokens[0], float): return Explicable_Decimal(Decimal(str(pstf_tokens[0]))) elif hasattr(pstf_tokens[0], 'STR_RENDER'): return pstf_tokens[0] else: return Expression(self) else: return Expression(pstf_tokens) 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)) @classmethod def tmp_render(cls, render=lambda _, x: pstf_factory(x)): # def tmp_render(cls, render=lambda _, x: Expression(x)): """ Same ad tmp_render for Renderable but default render is Expression >>> exp = Expression("2*3/5") >>> print(exp) 2 \\times \\frac{ 3 }{ 5 } >>> for i in exp.simplify().explain(): ... print(i) 2 \\times \\frac{ 3 }{ 5 } \\frac{ 3 }{ 5 } \\times 2 \\frac{ 3 \\times 2 }{ 5 } \\frac{ 6 }{ 5 } >>> with Expression.tmp_render(): ... for i in exp.simplify().explain(): ... i < [2, 3, 5, /, *] > < [2, < Fraction 3 / 5>, *] > < [< Fraction 3 / 5>, 2, *] > < [3, 2, *, 5, /] > < [6, 5, /] > >>> from .render import txt >>> with Expression.tmp_render(txt): ... for i in exp.simplify().explain(): ... print(i) 2 * 3 / 5 3 / 5 * 2 ( 3 * 2 ) / 5 6 / 5 >>> for i in exp.simplify().explain(): ... print(i) 2 \\times \\frac{ 3 }{ 5 } \\frac{ 3 }{ 5 } \\times 2 \\frac{ 3 \\times 2 }{ 5 } \\frac{ 6 }{ 5 } """ return super(Expression, cls).tmp_render(render) def __new__(cls, exp): """Create Expression objects :param exp: the expression. It can be a string or a list of postfix tokens. """ expression = object.__new__(cls) if isinstance(exp, str): expression.postfix_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 ] expression.postfix_tokens = flatten_list( [tok.postfix_tokens if Expression.isExpression(tok) else tok for tok in exp_mod_op] ) elif isinstance(exp, Expression): return exp elif isNumerand(exp): expression.postfix_tokens = [exp] else: raise ValueError( "Can't build Expression with {} object".format( type(exp) ) ) expression._isExpression = 1 return expression 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) def __repr__(self): return " ".join(["<", str(self.__class__), str(self.postfix_tokens), ">"]) 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.steps = self.child.steps + self.simplified.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 = Expression(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 = self.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) def develop_steps(self, tokenList): """ From a list of tokens, it develops steps of each tokens """ tmp_steps = [] for t in tokenList: try: with Expression.tmp_render(): tmp_steps.append([i for i in t.explain()]) except AttributeError: tmp_steps.append([t]) if max([len(i) for i in tmp_steps]) == 1: # Cas où rien n'a dû être expliqué. return [] else: tmp_steps = expand_list(tmp_steps)[:-1] steps = [Expression(s) for s in tmp_steps] return steps @classmethod def isExpression(self, 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 if __name__ == '__main__': print('\n') A = Expression("( -8 x + 8 ) ( -8 - ( -6 x ) )") Ar = A.simplify() for i in Ar.explain(): print(i) # print("------------") # for i in Ar.explain(): # print(i) # print(type(Ar)) # print('\n-----------') #A = Expression("-6 / 3 + 10 / -5") #Ar = A.simplify() # for i in Ar.explain(): # print(i) # print('\n-----------') #A = Expression("1/3 + 4/6") #Ar = A.simplify() # for i in Ar.explain(): # print(i) #import doctest # doctest.testmod() # ----------------------------- # Reglages pour 'vim' # vim:set autoindent expandtab tabstop=4 shiftwidth=4: # cursor: 16 del