From 6a6070d3d7a6f99379f258eded5cbb978fb93e4e Mon Sep 17 00:00:00 2001 From: lafrite Date: Sat, 8 Nov 2014 17:40:02 +0100 Subject: [PATCH] str2tokens seems to works --- pymath/operator.py | 8 ++- pymath/str2tokens.py | 131 +++++++++++++++++++++++++++++++++------- test/test_str2tokens.py | 42 +++++++++++++ 3 files changed, 157 insertions(+), 24 deletions(-) create mode 100644 test/test_str2tokens.py diff --git a/pymath/operator.py b/pymath/operator.py index 3deba4a..dd0cc6a 100644 --- a/pymath/operator.py +++ b/pymath/operator.py @@ -8,7 +8,8 @@ class Operator(str): """The operator class, is a string (representation of the operator) with its arity""" - OPERATORS = { \ + PRIORITY = {"^": 5, "/": 4, "*" : 3, ":": 3, "+": 2, "-":2, "(": 1} + OPERATIONS = { \ "+": ["", "", ("__add__","__radd__")],\ "-": ["", "__neg__", ("__sub__", "__rsub__")], \ "*": ["", "", ("__mul__", "__rmul__")], \ @@ -20,7 +21,10 @@ class Operator(str): op = str.__new__(cls, operator) op.arity = arity - op.actions = cls.OPERATORS[operator][arity] + # TODO: Add op.visibility |sam. nov. 8 17:00:08 CET 2014 + + op.priority = cls.PRIORITY[operator] + op.actions = cls.OPERATIONS[operator][arity] return op diff --git a/pymath/str2tokens.py b/pymath/str2tokens.py index 8c20f27..2de04ff 100644 --- a/pymath/str2tokens.py +++ b/pymath/str2tokens.py @@ -1,9 +1,24 @@ #!/usr/bin/env python # encoding: utf-8 +from .operator import Operator +from .generic import Stack +def str2tokens(exp): + """ Parse the string into tokens then turn it into postfix form + + >>> str2tokens('2+3*4') + [2, 3, 4, '*', '+'] + + >>> str2tokens('2*3+4') + [2, 3, '*', 4, '+'] + """ + in_tokens = str2in_tokens(exp) + post_tokens = in2post_fix(in_tokens) -def str2tokens(self, exp): + return post_tokens + +def str2in_tokens(exp): """ Parse the expression, ie tranform a string into a list of tokens /!\ float are not availiable yet! @@ -11,6 +26,10 @@ def str2tokens(self, exp): :param exp: The expression (a string) :returns: list of token + >>> str2tokens('2+3*4') + ['2', '+', '3', '*', '4'] + >>> str2tokens('2*3+4') + ['2', '*', '3', '+', '4'] """ tokens = [''] @@ -32,13 +51,14 @@ def str2tokens(self, exp): tokens.append(int(character)) elif character in "+-*/):^": - tokens.append(character) + tokens.append(Operator(character)) elif character in "(": # If "3(", ")(" - if self.isNumber(tokens[-1]) \ + if isNumber(tokens[-1]) \ or tokens[-1] == ")" : - tokens.append("*") + #tokens.append(Operator("*")) + tokens.append(Operator("*")) tokens.append(character) elif character == ".": @@ -51,41 +71,108 @@ def str2tokens(self, exp): -def in2post_fix(cls, infix_tokens): +def in2post_fix(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. - >>> .in2post_fix(['(', 2, '+', 5, '-', 1, ')', '/', '(', 3, '*', 4, ')']) - [2, 5, '+', 1, '-', 3, 4, '*', '/'] - """ - opStack = Stack() - postfixList = [] + >>> a, s, m, d, p = Operator("+"), Operator("-"), Operator("*"), Operator("/"), Operator("^") - for token in infix_tokens: + >>> in2post_fix(['(', 2, '+', 5, '-', 1, ')', '/', '(', 3, '*', 4, ')']) + [2, 5, '+', 1, '-', 3, 4, '*', '/'] + >>> in2post_fix(['-', '(', '-', 2, ')']) + [2, '-', '-'] + >>> in2post_fix(['-', '(', '-', 2, '+', 3, '*', 4, ')']) + [2, '-', 3, 4, '*', '+', '-'] + """ + # 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 == ")": - topToken = opStack.pop() - while topToken != "(": - postfixList.append(topToken) - topToken = opStack.pop() - elif cls.isOperator(token): - # On doit ajouter la condition == str sinon python ne veut pas tester l'appartenance à la chaine de caractère. - while (not opStack.isEmpty()) and (cls.PRIORITY[opStack.peek()] >= cls.PRIORITY[token]): - postfixList.append(opStack.pop()) + 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 isOperator(token): + while (not opStack.isEmpty()) and (opStack.peek().priority >= token.priority): + op = opStack.pop() + postfix_tokens.append(op) + + arity = arity_Stack.pop() + + token.arity = arity + 1 opStack.push(token) + # 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: - postfixList.append(token) + postfix_tokens.append(token) + arity = arity_Stack.pop() + arity_Stack.push(arity + 1) while not opStack.isEmpty(): - postfixList.append(opStack.pop()) + op = opStack.pop() + postfix_tokens.append(op) - return postfixList + # # 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 +def isOperator(exp): + """Check if the expression is an opération in "+-*/:^" + :param exp: an expression + :returns: boolean + + """ + + return type(exp) == Operator + #return (type(exp) == str and exp in "+-*/:^") + +def isNumber(exp): + """Check if the expression can be a number + + :param exp: an expression + :returns: True if the expression can be a number and false otherwise + + """ + return type(exp) == int + +if __name__ == '__main__': + #a, s, m, d, p = Operator("+"), Operator("-"), Operator("*"), Operator("/"), Operator("^") + #in_tokens = str2in_tokens("2+3*4") + #print("\t in_tokens :" + str(in_tokens)) + # + #print(in2post_fix(in_tokens)) + + import doctest + doctest.testmod() # ----------------------------- diff --git a/test/test_str2tokens.py b/test/test_str2tokens.py new file mode 100644 index 0000000..e2d0270 --- /dev/null +++ b/test/test_str2tokens.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# encoding: utf-8 + + +import unittest + +from pymath.str2tokens import str2tokens, str2in_tokens, in2post_fix + +class TestStr2tokens(unittest.TestCase): + """Testing functions from pymath.str2tokens""" + + def test_str2intokens(self): + ans = str2in_tokens("2+3*4") + self.assertEqual(ans, [2, "+", 3, "*", 4]) + + ans = str2in_tokens("2*3+4") + self.assertEqual(ans, [2, "*", 3, "+", 4]) + + + def test_in2post_fix(self): + in_tokens = str2in_tokens("2+3*4") + ans = in2post_fix(in_tokens) + self.assertEqual(ans, [2, 3, 4, "*", "+"]) + + in_tokens = str2in_tokens("2*3+4") + ans = in2post_fix(in_tokens) + self.assertEqual(ans, [2, 3,"*", 4, "+"]) + + + # TODO: Ajouter des tests pour les cas particuliers... |sam. nov. 8 17:39:18 CET 2014 + + + +if __name__ == '__main__': + unittest.main() + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del +