diff --git a/expression.py b/expression.py index 861fb4b..cf40ad0 100644 --- a/expression.py +++ b/expression.py @@ -4,6 +4,8 @@ from generic import Stack, flatten_list, expand_list from fraction import Fraction from render import txt_render, post2in_fix, tex_render +from formal import FormalExp +from formal import FormalExp class Expression(object): """A calculus expression. Today it can andle only expression with numbers later it will be able to manipulate unknown""" @@ -32,7 +34,7 @@ class Expression(object): def render(self, render = lambda x:str(x)): """ Same as __str__ but accept render as argument - @param render: function which render the list of token (postfix form) to string + :param render: function which render the list of token (postfix form) to string """ # TODO: I don't like the name of this method |ven. janv. 17 12:48:14 CET 2014 @@ -44,7 +46,7 @@ class Expression(object): def simplify(self, render = lambda x:str(x)): """ Generator which return steps for computing the expression - @param render: function which render the list of token (postfix form now) to string + :param render: function which render the list of token (postfix form now) to string """ if not self.can_go_further(): @@ -123,8 +125,10 @@ class Expression(object): for character in exp: if character.isdigit(): + # for "big" numbers (like 2345) if type(tokens[-1]) == int: tokens[-1] = tokens[-1]*10 + int(character) + # Special case for "-" at the begining of an expression or before "(" elif tokens[-1] == "-" and \ str(tokens[-2]) in " (": @@ -133,16 +137,29 @@ class Expression(object): tokens.append(int(character)) elif character.isalpha(): - if str(tokens[-1]).isalpha(): - tokens[-1] += character + # If "3x", ")x" or "yx" + if self.isNumber(tokens[-1]) \ + or tokens[-1] == ")" \ + or type(tokens[-1]) == FormalExp: + tokens.append("*") + tokens.append(FormalExp(letter = character)) + + # Special case for "-" at the begining of an expression or before "(" + elif tokens[-1] == "-" \ + or str(tokens[-2]) in " (": + tokens[-1] = - FormalExp(letter = character) + else: - tokens.append(character) + tokens.append(FormalExp(letter = character)) elif character in "+-*/)": tokens.append(character) elif character in "(": - if self.isNumber(tokens[-1]) or tokens[-1] == ")": + # If "3(", ")(" or "x(" + if self.isNumber(tokens[-1]) \ + or tokens[-1] == ")" \ + or type(tokens[-1]) == FormalExp: tokens.append("*") tokens.append(character) @@ -278,13 +295,18 @@ class Expression(object): :returns: string representing the result """ - operations = {"+": "__add__", "-": "__sub__", "*": "__mul__"} if op == "/": ans = [Fraction(op1, op2)] ans += ans[0].simplify() return ans else: - return getattr(op1,operations[op])(op2) + if type(op2) != int: + operations = {"+": "__radd__", "-": "__rsub__", "*": "__rmul__"} + return getattr(op2,operations[op])(op1) + else: + operations = {"+": "__add__", "-": "__sub__", "*": "__mul__"} + return getattr(op1,operations[op])(op2) + ## --------------------- ## Recognize numbers and operators @@ -299,7 +321,7 @@ class Expression(object): """ return type(exp) == int or \ type(exp) == Fraction or \ - exp.isalpha() + type(exp) == FormalExp @staticmethod def isOperator(exp): @@ -322,17 +344,17 @@ def test(exp): print("\n") if __name__ == '__main__': - exp = "1 + 3 * 5" - test(exp) + #exp = "1 + 3 * 5" + #test(exp) #exp = "2 * 3 * 3 * 5" #test(exp) - exp = "2 * 3 + 3 * 5" - test(exp) + #exp = "2 * 3 + 3 * 5" + #test(exp) - exp = "2 * ( 3 + 4 ) + 3 * 5" - test(exp) + #exp = "2 * ( 3 + 4 ) + 3 * 5" + #test(exp) #exp = "2 * ( 3 + 4 ) + ( 3 - 4 ) * 5" #test(exp) @@ -352,25 +374,29 @@ if __name__ == '__main__': #exp = "( 2 + 5 ) * ( 3 * 4 )" #test(exp) - exp = "( 2 + 5 - 1 ) / ( 3 * 4 )" - test(exp) + #exp = "( 2 + 5 - 1 ) / ( 3 * 4 )" + #test(exp) - exp = "( 2 + 5 ) / ( 3 * 4 ) + 1 / 12" - test(exp) + #exp = "( 2 + 5 ) / ( 3 * 4 ) + 1 / 12" + #test(exp) - exp = "( 2+ 5 )/( 3 * 4 ) + 1 / 2" - test(exp) + #exp = "( 2+ 5 )/( 3 * 4 ) + 1 / 2" + #test(exp) - exp="(-2+5)/(3*4)+1/12+5*5" - test(exp) + #exp="(-2+5)/(3*4)+1/12+5*5" + #test(exp) exp="-2*4(12 + 1)(3-12)" test(exp) - exp="-2*a(12 + 1)(3-12)" + exp="-2+a+(12 + 1)(3-12) + 34a" + test(exp) e = Expression(exp) print(e) + #exp="-2*b+a(12 + 1)(3-12)" + #test(exp) + # TODO: The next one doesn't work |ven. janv. 17 14:56:58 CET 2014 #exp="-2*(-a)(12 + 1)(3-12)" #e = Expression(exp) diff --git a/formal.py b/formal.py new file mode 100644 index 0000000..ca837c5 --- /dev/null +++ b/formal.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python +# encoding: utf-8 + +from fraction import Fraction +from generic import add_in_dict, remove_in_dict +import re + +class FormalExp(object): + """A formal expression (similare to Symbol in Sympy""" + + def __init__(self, coef = {}, letter = ""): + """Initiat the formal expression + + :param coef: the dictionary representing the expression + :param letter: minimum expression, a letter + + """ + + if coef != {} and letter != "": + raise ValueError("A FormalExp can't be initiate with dict_exp and a letter") + elif letter != "": + self._letter = letter + self._coef = {letter: 1} + elif coef != {}: + self._coef = coef + else: + raise ValueError("FormalExp needs a letter or dictionary of coeficients") + + if len(self) != 1: + self.mainOp = "+" + + def master_coef(self): + """Return the master coefficient + /!\ may not work pretty well if there is more than one indeterminate + :returns: a_n + + """ + pattern = "\w\*\*(\d*)" + finder = re.compile(pattern) + power = {} + for (k,v) in self._coef.items(): + if k=="": + power[0] = v + else: + p = finder.findall(k) + if p == []: + power[1] = v + else: + power[int(p[0])] = v + + m_power = max(power) + return power[m_power] + + def __add__(self, other): + if type(other) in [int, Fraction]: + d = {"":other} + elif type(other) == FormalExp: + d = other._coef + else: + raise ValueError("Can't add {type} with FormalExp".format(type=type(other))) + + d = add_in_dict(self._coef, d) + d = remove_in_dict(d) + if list(d.keys()) == ['']: + return [d['']] + else: + return [FormalExp(d)] + + def __radd__(self, other): + return self + other + + def __sub__(self, other): + o_tmp = -other + return self + o_tmp + + def __neg__(self): + d = {} + for k,v in self._coef.items(): + d[k] = -v + return FormalExp(d) + + def __mul__(self, other): + pass + + def __rmul__(self, other): + pass + + def __div__(self, other): + pass + + def __pow__(self, other): + pass + + def __len__(self): + return len(list(self._coef.keys())) + + def __str__(self): + return " + ".join([str(v) + str(k) for k,v in self._coef.items()]) + +if __name__ == '__main__': + #fe1 = FormalExp({"x": 1, "":2}) + #print(fe1) + #fe2 = FormalExp({"x**12": 5, "":2}) + #print(fe2) + #fe3 = fe1 + fe2 + #for s in fe3: + # print(s) + #fe4 = fe1 + 2 + #for s in fe4: + # print(s) + + #print(fe1.master_coef()) + #print(fe2.master_coef()) + #print(fe3[0].master_coef()) + #print(fe4[0].master_coef()) + + fe = FormalExp(letter = "a") + fe_ = -2 + fe + print(fe_[0]) + + + + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del diff --git a/generic.py b/generic.py index 8cdf749..745ebcd 100644 --- a/generic.py +++ b/generic.py @@ -99,6 +99,54 @@ def expand_list(list_list): ans = [list_list] return ans + +def add_in_dict(dict1, dict2): + """Merge dictionary keys and add the content from dict1 and dict2 + + :param dict1: first dictionary + :param dict2: second dictionary + :returns: merged and added dictionary + + >>> add_in_dict({'a':1, 'b':2}, {'c':3, 'd': 4}) == {'d': 4, 'a': 1, 'c': 3, 'b': 2} + True + >>> add_in_dict({'a':1, 'b':2}, {'a':3, 'b': 4}) == {'a': 4, 'b': 6} + True + >>> add_in_dict({'a':1, 'b':2}, {'a':3, 'c': 4}) == {'a': 4, 'b': 2, 'c': 4} + True + + """ + new_dict = {} + new_dict.update(dict1) + for (k,v) in dict2.items(): + if k in new_dict.keys(): + new_dict[k] += v + else: + new_dict[k] = v + + return new_dict + +def remove_in_dict(d, value = 0): + """ In a dictionary, remove keys which have certain value + + :param d: the dictionary + :param value: value to remove + :returns: new dictionary whithout unwanted value + + >>> remove_in_dict({'b': 1, 'a': 0}) == {'b': 1} + True + >>> remove_in_dict({'b': 1, 'a': 0}, 1) == {'a': 0} + True + """ + new_dict = {} + for (k,v) in d.items(): + if v != value: + new_dict[k] = v + return new_dict + +if __name__ == '__main__': + import doctest + doctest.testmod() + # ----------------------------- # Reglages pour 'vim' # vim:set autoindent expandtab tabstop=4 shiftwidth=4: diff --git a/render.py b/render.py index 191ca05..05bf1ce 100644 --- a/render.py +++ b/render.py @@ -3,6 +3,7 @@ from generic import Stack,flatten_list from fraction import Fraction +from formal import FormalExp @@ -16,7 +17,7 @@ class Render(object): PRIORITY = {"*" : 3, "/": 3, "+": 2, "-":2, "(": 1} - def __init__(self, op_infix = {}, op_postfix = {}, other = {}, join = " ", type_render = {int: str, Fraction: str, str: str}): + def __init__(self, op_infix = {}, op_postfix = {}, other = {}, join = " ", type_render = {int: str, Fraction: str, FormalExp: str}): """Initiate the render @param op_infix: the dictionnary of infix operator with how they have to be render @@ -70,12 +71,13 @@ class Render(object): else: operandeStack.push(token) + # Manip pour gerer les cas de listes imbriquées dans d'autres listes infix_tokens = operandeStack.pop() if type(infix_tokens) == list or type(infix_tokens) == flist: infix_tokens = flatten_list(infix_tokens) - elif self.isNumber(infix_tokens): + elif self.isNumerande(infix_tokens): infix_tokens = [infix_tokens] if self.join: @@ -84,13 +86,13 @@ class Render(object): return infix_tokens def render_from_type(self, op): - """ If the op is a number, it transforms it with type_render conditions + """ If the op is a numerande, it transforms it with type_render conditions :param op: the operator :returns: the op transformed if it's necessary """ - if self.isNumber(op): + if self.isNumerande(op): return self.type_render[type(op)](op) else: return op @@ -108,9 +110,17 @@ class Render(object): :returns: bollean """ if self.isNumber(operande) \ - and type(operande) != str \ and operande < 0: return 1 + + elif type(operande) == FormalExp: + if operator in ["*", "/"]: + if len(operande) > 1 \ + or operande.master_coef() < 0: + return 1 + else: + return 0 + elif not self.isNumber(operande): # Si c'est une grande expression ou un chiffre négatif stand_alone = self.get_main_op(operande) @@ -155,17 +165,28 @@ class Render(object): @staticmethod def isNumber( exp): - """Check if the expression can be a number which means that it is not a operator + """Check if the expression can be a number which means int or Fraction :param exp: an expression :returns: True if the expression can be a number and false otherwise """ - return type(exp) == int or \ - type(exp) == Fraction or \ - (type(exp) == str and exp.isalpha()) + return type(exp) == int \ + or type(exp) == Fraction #return type(exp) == int or type(exp) == Fraction + @staticmethod + def isNumerande(exp): + """Check if the expression can be a numerande (not an operator) + + :param exp: an expression + :returns: True if the expression can be a number and false otherwise + + """ + return type(exp) == int \ + or type(exp) == Fraction \ + or type(exp) == FormalExp + def isOperator(self, exp): """Check if the expression is in self.operators @@ -202,9 +223,9 @@ post2in_fix = Render(p2i_infix, p2i_postfix, p2i_other, join = False) # A latex render def texSlash(op1, op2): - if not Render.isNumber(op1) and op1[0] == "(" and op1[-1] == ")": + if not Render.isNumerande(op1) and op1[0] == "(" and op1[-1] == ")": op1 = op1[1:-1] - if not Render.isNumber(op2) and op2[0] == "(" and op2[-1] == ")": + if not Render.isNumerande(op2) and op2[0] == "(" and op2[-1] == ")": op2 = op2[1:-1] return ["\\frac{" , op1 , "}{" , op2 , "}"] @@ -214,7 +235,7 @@ def texFrac(frac): tex_infix = {"+": " + ", "-": " - ", "*": " \\times "} tex_postfix = {"/": texSlash} tex_other = {"(": "(", ")": ")"} -tex_type_render = {int: str, Fraction: texFrac, str: str} +tex_type_render = {int: str, Fraction: texFrac, FormalExp: str} tex_render = Render(tex_infix, tex_postfix, tex_other, type_render = tex_type_render)