#!/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