diff --git a/pymath/expression.py b/pymath/expression.py index c800d6e..1e3bd0e 100644 --- a/pymath/expression.py +++ b/pymath/expression.py @@ -2,6 +2,7 @@ # encoding: utf-8 from .generic import Stack, flatten_list, expand_list +from .operator import Operator from .fraction import Fraction from .renders import txt, post2in_fix, tex @@ -81,20 +82,36 @@ class Expression(object): tmpTokenList = [] while len(tokenList) > 2: - # on va chercher les motifs du genre A B + pour les calculer - if self.isNumber(tokenList[0]) and self.isNumber(tokenList[1]) and self.isOperator(tokenList[2]): + # on va chercher les motifs du genre A B +, quad l'operateur est d'arité 2, pour les calculer + if self.isNumber(tokenList[0]) and self.isNumber(tokenList[1]) \ + and type(tokenList[2]) == Operator and tokenList[2].arity == 2 : # S'il y a une opération à faire op1 = tokenList[0] op2 = tokenList[1] - token = tokenList[2] + operator = tokenList[2] - res = self.doMath(token, op1, op2) + 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] + + elif self.isNumber(tokenList[0]) \ + and type(tokenList[1]) == Operator 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]) @@ -190,31 +207,73 @@ class Expression(object): # "fix" tranformations @classmethod - ## --------------------- - ## Computing the expression - - @staticmethod - def doMath(op, op1, op2): - """Compute "op1 op op2" or create a fraction - - :param op: operator - :param op1: first operande - :param op2: second operande - :returns: string representing the result + def in2post_fix(cls, infix_tokens): + """ From the infix_tokens list compute the corresponding postfix_tokens list + + @param infix_tokens: the infix list of tokens to transform into postfix form. + @return: the corresponding postfix list of tokens. + >>> Expression.in2post_fix(['(', 2, '+', 5, '-', 1, ')', '/', '(', 3, '*', 4, ')']) + [2, 5, '+', 1, '-', 3, 4, '*', '/'] + >>> Expression.in2post_fix(['-', '(', '-', 2, ')']) + [2, '-', '-'] + >>> Expression.in2post_fix(['-', '(', '-', 2, '+', 3, "*", 4, ')']) + [2, '-', 3, 4, '*', '+', '-'] """ - if op == "/": - ans = [Fraction(op1, op2)] - ans += ans[0].simplify() - return ans - else: - if type(op2) != int: - operations = {"+": "__radd__", "-": "__rsub__", "*": "__rmul__"} - return getattr(op2,operations[op])(op1) - else: - operations = {"+": "__add__", "-": "__sub__", "*": "__mul__", "^": "__pow__"} - return getattr(op1,operations[op])(op2) + # Stack where operator will be stocked + opStack = Stack() + # final postfix list of tokens + postfix_tokens = [] + # stack with the nbr of tokens still to compute in postfix_tokens + arity_Stack = Stack() + arity_Stack.push(0) + for (pos_token,token) in enumerate(infix_tokens): + + # # Pour voir ce qu'il se passe dans cette procédure + # print(str(postfix_tokens), " | ", str(opStack), " | ", str(infix_tokens[(pos_token+1):]), " | ", str(arity_Stack)) + if token == "(": + opStack.push(token) + # Set next arity counter + arity_Stack.push(0) + elif token == ")": + op = opStack.pop() + while op != "(": + postfix_tokens.append(op) + op = opStack.pop() + + # Go back to old arity + arity_Stack.pop() + # Raise the arity + arity = arity_Stack.pop() + arity_Stack.push(arity + 1) + + elif cls.isOperator(token): + while (not opStack.isEmpty()) and (cls.PRIORITY[opStack.peek()] >= cls.PRIORITY[token]): + op = opStack.pop() + postfix_tokens.append(op) + + arity = arity_Stack.pop() + opStack.push(Operator(token, arity + 1)) + # print("--", token, " -> ", str(arity + 1)) + # Reset arity to 0 in case there is other operators (the real operation would be "-op.arity + 1") + arity_Stack.push(0) + else: + postfix_tokens.append(token) + arity = arity_Stack.pop() + arity_Stack.push(arity + 1) + + while not opStack.isEmpty(): + op = opStack.pop() + postfix_tokens.append(op) + + # # Pour voir ce qu'il se passe dans cette procédure + # print(str(postfix_tokens), " | ", str(opStack), " | ", str(infix_tokens[(pos_token+1):]), " | ", str(arity_Stack)) + + if arity_Stack.peek() != 1: + raise ValueError("Unvalid expression. The arity Stack is ", str(arity_Stack)) + + return postfix_tokens ## --------------------- ## Recognize numbers and operators @@ -250,11 +309,11 @@ def test(exp): if __name__ == '__main__': Expression.STR_RENDER = txt - #exp = "2 ^ 3 * 5" - #test(exp) + exp = "2 ^ 3 * 5" + test(exp) - #exp = "1 + 3 * 5" - #test(exp) + exp = "1 + 3 * 5" + test(exp) #exp = "2 * 3 * 3 * 5" #test(exp) diff --git a/pymath/generic.py b/pymath/generic.py index f1a43ef..959c66a 100644 --- a/pymath/generic.py +++ b/pymath/generic.py @@ -57,7 +57,6 @@ class Stack(object): def __add__(self, addList): return self.items + addList - def flatten_list(a, result=None): """Flattens a nested list. diff --git a/pymath/operator.py b/pymath/operator.py new file mode 100644 index 0000000..3deba4a --- /dev/null +++ b/pymath/operator.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# encoding: utf-8 + + +from .fraction import Fraction + +class Operator(str): + + """The operator class, is a string (representation of the operator) with its arity""" + + OPERATORS = { \ + "+": ["", "", ("__add__","__radd__")],\ + "-": ["", "__neg__", ("__sub__", "__rsub__")], \ + "*": ["", "", ("__mul__", "__rmul__")], \ + "/": ["", "", ("__div__","__rdiv__")], \ + "^": ["", "", ("__pow__", "")] \ + } + + def __new__(cls, operator, arity = 2): + op = str.__new__(cls, operator) + op.arity = arity + + op.actions = cls.OPERATORS[operator][arity] + + return op + + def __call__(self, *args): + """ Calling this operator performs the rigth calculus """ + if self.arity == 1: + return getattr(args[0], self.actions)() + + elif self.arity == 2: + # C'est moche mais je veux que ça marche... + if str(self) == "/": + ans = [Fraction(args[0], args[1])] + ans += ans[0].simplify() + return ans + else: + if type(args[1]) == int: + return getattr(args[0], self.actions[0])(args[1]) + else: + return getattr(args[1], self.actions[1])(args[0]) + + + + + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del diff --git a/pymath/render.py b/pymath/render.py index 24c36e8..05fa577 100644 --- a/pymath/render.py +++ b/pymath/render.py @@ -3,6 +3,7 @@ from .generic import Stack,flatten_list from .fraction import Fraction +from .operator import Operator __all__ = ['Render'] @@ -195,7 +196,7 @@ class Render(object): :returns: boolean """ - return (type(exp) == str and exp in self.operators) + return (type(exp) == Operator and str(exp) in self.operators) class flist(list): """Fake list- they are used to stock the main operation of an rendered expression""" diff --git a/test/test_expression.py b/test/test_expression.py index c4126bb..03716d0 100644 --- a/test/test_expression.py +++ b/test/test_expression.py @@ -56,18 +56,6 @@ class TestExpression(unittest.TestCase): exp = "1 + $" self.assertRaises(ValueError, Expression.str2tokens, exp) - def test_doMath(self): - ops = [\ - {"op": ("+", 1 , 2), "res" : 3}, \ - {"op": ("-", 1 , 2), "res" : -1}, \ - {"op": ("*", 1 , 2), "res" : 2}, \ - {"op": ("/", 1 , 2), "res" : Fraction(1,2)}, \ - {"op": ("^", 1 , 2), "res" : 1}, \ - ] - for op in ops: - res = first_elem(Expression.doMath(*op["op"])) - self.assertAlmostEqual(res, op["res"]) - def test_isNumber(self): pass diff --git a/test/test_renders.py b/test/test_renders.py index 8f0bec4..6453dff 100644 --- a/test/test_renders.py +++ b/test/test_renders.py @@ -6,6 +6,7 @@ import unittest from pymath.renders import tex, txt from pymath.fraction import Fraction +from pymath.operator import Operator @@ -22,21 +23,21 @@ class TestTexRender(unittest.TestCase): self.assertEqual(tex([Fraction(1,2)]), "\\frac{ 1 }{ 2 }") def test_mult_interger(self): - exps = [ [2, 3, "*"], [2, -3, "*"], [-2, 3, "*"]] + exps = [ [2, 3, Operator("*", 2)], [2, -3, Operator("*", 2)], [-2, 3, Operator("*", 2)]] wanted_render = [ "2 \\times 3", "2 \\times ( -3 )", "-2 \\times 3"] for (i,e) in enumerate(exps): rend = tex(e) self.assertEqual(rend, wanted_render[i]) def test_mult_letter(self): - exps = [ [2, "a", "*"], ["a", 3, "*"], [-2, "a", "*"], ["a", -2, "*"]] + exps = [ [2, "a", Operator("*", 2)], ["a", 3, Operator("*", 2)], [-2, "a", Operator("*", 2)], ["a", -2, Operator("*", 2)]] wanted_render = [ "2 a", "a \\times 3", "-2 a", "a \\times ( -2 )"] for (i,e) in enumerate(exps): rend = tex(e) self.assertEqual(rend, wanted_render[i]) def test_mult_fraction(self): - exps = [ [2, Fraction(1,2), "*"], [Fraction(1,2), 3, "*"]] + exps = [ [2, Fraction(1,2), Operator("*", 2)], [Fraction(1,2), 3, Operator("*", 2)]] wanted_render = [ "2 \\times \\frac{ 1 }{ 2 }", "\\frac{ 1 }{ 2 } \\times 3"] for (i,e) in enumerate(exps): rend = tex(e) @@ -64,21 +65,27 @@ class TesttxtRender(unittest.TestCase): self.assertEqual(txt([Fraction(1,2)]), "1 / 2") def test_mult_interger(self): - exps = [ [2, 3, "*"], [2, -3, "*"], [-2, 3, "*"]] + exps = [ [2, 3, Operator("*", 2)], \ + [2, -3, Operator("*", 2)], \ + [-2, 3, Operator("*", 2)]] wanted_render = [ "2 * 3", "2 * ( -3 )", "-2 * 3"] for (i,e) in enumerate(exps): rend = txt(e) self.assertEqual(rend, wanted_render[i]) def test_mult_letter(self): - exps = [ [2, "a", "*"], ["a", 3, "*"], [-2, "a", "*"], ["a", -2, "*"]] + exps = [ [2, "a", Operator("*", 2)], \ + ["a", 3, Operator("*", 2)], \ + [-2, "a", Operator("*", 2)], \ + ["a", -2, Operator("*", 2)]] wanted_render = [ "2 a", "a * 3", "-2 a", "a * ( -2 )"] for (i,e) in enumerate(exps): rend = txt(e) self.assertEqual(rend, wanted_render[i]) def test_mult_fraction(self): - exps = [ [2, Fraction(1,2), "*"], [Fraction(1,2), 3, "*"]] + exps = [ [2, Fraction(1,2), Operator("*", 2)], \ + [Fraction(1,2), 3, Operator("*", 2)]] wanted_render = [ "2 * 1 / 2", "1 / 2 * 3"] for (i,e) in enumerate(exps): rend = txt(e)