diff --git a/.gitignore b/.gitignore index 7a60b85..86fff81 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ __pycache__/ *.pyc +dist/ diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..5ce56e9 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,9 @@ +# file GENERATED by distutils, do NOT edit +setup.py +pymath/__init__.py +pymath/arithmetic.py +pymath/expression.py +pymath/fraction.py +pymath/generic.py +pymath/random_expression.py +pymath/render.py diff --git a/TODO b/TODO new file mode 100644 index 0000000..375517a --- /dev/null +++ b/TODO @@ -0,0 +1,9 @@ +# Todolist + +* Improve fix recognition (DONE) +* More flexible expression parsing (DONE) +* bug: expression can't handle -(-2) +* Overload + - * for expression (DONE ~ no steps yet) +* Expression should be able to simplify expression with ":" + +* Expression parents class and his children: Numerical_exp, toGenerate_exp and formal expression diff --git a/arithmetic.py b/arithmetic.py deleted file mode 100644 index b559788..0000000 --- a/arithmetic.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 - - -def gcd(a, b): - """Compute gcd(a,b) - - :param a: first number - :param b: second number - :returns: the gcd - - """ - if a > b: - c = a % b - else: - c = b % a - - if c == 0: - return min(a,b) - elif a == 1: - return b - elif b == 1: - return a - else: - return gcd(min(a,b), c) - - -# ----------------------------- -# Reglages pour 'vim' -# vim:set autoindent expandtab tabstop=4 shiftwidth=4: -# cursor: 16 del diff --git a/calculus.py b/calculus.py deleted file mode 100644 index 19d2fcc..0000000 --- a/calculus.py +++ /dev/null @@ -1,409 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 - - -from generic import Stack -from fraction import Fraction - -def str2tokens(exp): - """Convert an expression into a list of tokens - - :param exp: The expression - :returns: the list of tokens - - >>> str2tokens("1 + 2") - [1, '+', 2] - - """ - tokens = exp.split(" ") - - for (i,t) in enumerate(tokens): - try: - tokens[i] = int(t) - except ValueError: - pass - - return tokens - -def infixToPostfix(infixTokens): - """Transform an infix list of tokens into postfix tokens - - :param infixTokens: an infix list of tokens - :returns: the corresponding postfix list of tokens - - :Example: - - >>> infixToPostfix([1, "+", 2]) - [1, 2, '+'] - - >>> infixToPostfix([1, "*", 2, "+", 3]) - [1, 2, '*', 3, '+'] - """ - - priority = {"*" : 3, "/": 3, "+": 2, "-":2, "(": 1} - - opStack = Stack() - postfixList = [] - - #infixTokens = infixExp.split(" ") - - for token in infixTokens: - if token == "(": - opStack.push(token) - elif token == ")": - topToken = opStack.pop() - while topToken != "(": - postfixList.append(topToken) - topToken = opStack.pop() - elif isOperation(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 (priority[opStack.peek()] >= priority[token]): - postfixList.append(opStack.pop()) - opStack.push(token) - else: - postfixList.append(token) - - while not opStack.isEmpty(): - postfixList.append(opStack.pop()) - - return postfixList - -def computePostfix(postfixTokens): - """Compute a postfix list of tokens - - :param postfixTokens: a postfix list of tokens - :returns: the result of the calculus - - """ - #print(postfixToInfix(postfixExp)) - # where to save numbers or - operandeStack = Stack() - - #tokenList = postfixExp.split(" ") - - for (i,token) in enumerate(postfixTokens): - if isOperation(token): - op2 = operandeStack.pop() - op1 = operandeStack.pop() - res = doMath(token, op1, op2) - operandeStack.push(res) - - #print("Operation: {op1} {op} {op2}".format(op1 = op1, op = token, op2 = op2)) - #print(operandeStack) - #print(postfixTokens[i+1:]) - newPostfix = " ".join(operandeStack + postfixTokens[i+1:]) - #print(postfixToInfix(newPostfix)) - - else: - operandeStack.push(token) - - return operandeStack.pop() - -def computePostfixBis(postfixTokens): - """Compute a postfix list of tokens like a good student - - :param postfixTokens: a postfix list of tokens - :returns: the result of the expression - - """ - # where to save numbers or - operandeStack = Stack() - - #tokenList = postfixExp.split(" ") - tokenList = postfixTokens.copy() - - steps = [] - - steps = [] - - # On fait le calcul jusqu'à n'avoir plus qu'un élément - while len(tokenList) > 1: - tmpTokenList = [] - # on va chercher les motifs du genre A B + pour les calculer - while len(tokenList) > 2: - if isNumber(tokenList[0]) and isNumber(tokenList[1]) and isOperation(tokenList[2]): - # S'il y a une opération à faire - op1 = tokenList[0] - op2 = tokenList[1] - token = tokenList[2] - - res = doMath(token, 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] - else: - tmpTokenList.append(tokenList[0]) - - del tokenList[0] - tmpTokenList += tokenList - - steps += expand_list(tmpTokenList) - - tokenList = steps[-1].copy() - - - return steps - -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 or type(exp) == Fraction - -def isOperation(exp): - """Check if the expression is an opération in "+-*/" - - :param exp: an expression - :returns: boolean - - """ - return (type(exp) == str and exp in "+-*/") - - -def doMath(op, op1, op2): - """Compute "op1 op op2" - - :param op: operator - :param op1: first operande - :param op2: second operande - :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) - - - -def postfixToInfix(postfixTokens): - """Transforms postfix list of tokens into infix string - - :param postfixTokens: a postfix list of tokens - :returns: the corresponding infix string - - """ - operandeStack = Stack() - - #print(postfixTokens) - - #tokenList = postfixExp.split(" ") - - for (i,token) in enumerate(postfixTokens): - if isOperation(token): - op2 = operandeStack.pop() - if needPar(op2, token, "after"): - op2 = "( " + str(op2) + " )" - op1 = operandeStack.pop() - if needPar(op1, token, "before"): - op1 = "( " + str(op1) + " )" - res = "{op1} {op} {op2}".format(op1 = op1, op = token, op2 = op2) - - operandeStack.push(res) - - else: - operandeStack.push(token) - - return operandeStack.pop() - -def needPar(operande, operator, posi = "after"): - """Says whether or not the operande needs parenthesis - - :param operande: the operande - :param operator: the operator - :param posi: "after"(default) if the operande will be after the operator, "before" othewise - :returns: bollean - """ - - priority = {"*" : 3, "/": 3, "+": 2, "-":2} - - if isNumber(operande) and operande < 0: - return 1 - elif not isNumber(operande): - # Si c'est une grande expression ou un chiffre négatif - stand_alone = get_main_op(operande) - # Si la priorité de l'operande est plus faible que celle de l'opérateur - #debug_var("stand_alone",stand_alone) - #debug_var("operande", type(operande)) - minor_priority = priority[get_main_op(operande)] < priority[operator] - # Si l'opérateur est -/ pour after ou juste / pour before - special = (operator in "-/" and posi == "after") or (operator in "/" and posi == "before") - - return stand_alone and (minor_priority or special) - else: - return 0 - -def get_main_op(tokens): - """Getting the main operation of the list of tokens - - :param exp: the list of tokens - :returns: the main operation (+, -, * or /) or 0 if the expression is only one element - - """ - - priority = {"*" : 3, "/": 3, "+": 2, "-":2} - - parStack = Stack() - #tokenList = exp.split(" ") - - if len(tokens) == 1: - # Si l'expression n'est qu'un élément - return 0 - - main_op = [] - - for token in tokens: - if token == "(": - parStack.push(token) - elif token == ")": - parStack.pop() - elif isOperation(token) and parStack.isEmpty(): - main_op.append(token) - - return min(main_op, key = lambda s: priority[s]) - - -def expand_list(list_list): - """Expand list of list - - :param list: the list to expande - :returns: list of expanded lists - - :Example: - - >>> expand_list([1,2,[3,4],5,[6,7,8]]) - [[1, 2, 3, 5, 6], [1, 2, 4, 5, 7], [1, 2, 4, 5, 8]] - >>> expand_list([1,2,4,5,6,7,8]) - [[1, 2, 4, 5, 6, 7, 8]] - - """ - list_in_list = [i for i in list_list if type(i) == list].copy() - - try: - nbr_ans_list = max([len(i) for i in list_in_list]) - - ans = [list_list.copy() for i in range(nbr_ans_list)] - for (i,l) in enumerate(ans): - for (j,e) in enumerate(l): - if type(e) == list: - ans[i][j] = e[min(i,len(e)-1)] - # S'il n'y a pas eut d'étapes intermédiaires (2e exemple) - except ValueError: - ans = [list_list] - - return ans - -def print_steps(steps): - """Juste print a list - - :param steps: @todo - :returns: @todo - - """ - print("{first} \t = {sec}".format(first = str_from_postfix(steps[0]), sec = str_from_postfix(steps[1]))) - for i in steps[2:]: - print("\t\t = {i}".format(i=str_from_postfix(i))) - - -def str_from_postfix(postfix): - """Return the string representing the expression - - :param postfix: a postfix ordered list of tokens - :returns: the corresponding string expression - - """ - infix = postfixToInfix(postfix) - return infix - - -def debug_var(name, var): - """print the name of the variable and the value - - :param name: the name of the variable - :param var: the variable we want information - """ - print(name, ": ", var) - - -def test(exp): - """Make various test on an expression - - """ - print("-------------") - print("Expression ",exp) - tokens = str2tokens(exp) - postfix = infixToPostfix(tokens) - #print("Postfix " , postfix) - #print(computePostfix(postfix)) - #print("Bis") - steps = [postfix] - steps += computePostfixBis(postfix) - print_steps(steps) - #print(postfixToInfix(postfix)) - #print(get_main_op(exp)) - - -if __name__ == '__main__': - exp = "1 + 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 - 4 ) * 5" - #test(exp) - # - #exp = "2 * ( 2 - ( 3 + 4 ) ) + ( 3 - 4 ) * 5" - #test(exp) - # - #exp = "2 * ( 2 - ( 3 + 4 ) ) + 5 * ( 3 - 4 )" - #test(exp) - # - #exp = "2 + 5 * ( 3 - 4 )" - #test(exp) - # - #exp = "( 2 + 5 ) * ( 3 - 4 )" - #test(exp) - # - #exp = "( 2 + 5 ) * ( 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 / 2" - test(exp) - - exp = "( 2 + 5 ) / ( 3 * 4 ) + 1 / 12 + 5 * 5" - test(exp) - - #print(expand_list([1,2,['a','b','c'], 3, ['d','e']])) - - ## Ce denier pose un soucis. Pour le faire marcher il faudrai implémenter le calcul avec les fractions - #exp = "( 2 + 5 ) / 3 * 4" - #test(exp) - import doctest - doctest.testmod() - - -# ----------------------------- -# Reglages pour 'vim' -# vim:set autoindent expandtab tabstop=4 shiftwidth=4: -# cursor: 16 del diff --git a/generic.py b/generic.py deleted file mode 100644 index 8cdf749..0000000 --- a/generic.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 - - -class Stack(object): - """Docstring for Stack """ - - def __init__(self): - """@todo: to be defined1 """ - self.items = [] - - def pushFromList(self, list): - """Push the list in the stack - - :param list: a list - """ - for i in list[::-1]: - self.push(i) - - def isEmpty(self): - """ Says if the stack is empty - :returns: @todo - - """ - return self.items == [] - - def push(self, item): - """Push an item in the stack - - :param item: @todo - :returns: @todo - - """ - self.items.append(item) - - def pop(self): - """Getting the last item and remove it - :returns: last item - - """ - return self.items.pop() - - def peek(self, posi = 0): - """Getting the last item - :param posi: which item to peek 0 (last) 1 (the onebefore the last)... - :returns: the item - - """ - return self.items[-1 - posi] - - def __len__(self): - return len(self.items) - - def __str__(self): - return str(self.items) + " -> " - - def __add__(self, addList): - return self.items + addList - - -def flatten_list(a, result=None): - """Flattens a nested list. - - >>> flatten_list([ [1, 2, [3, 4] ], [5, 6], 7]) - [1, 2, 3, 4, 5, 6, 7] - """ - if result is None: - result = [] - - for x in a: - if isinstance(x, list): - flatten_list(x, result) - else: - result.append(x) - - return result - -def expand_list(list_list): - """Expand list of list - - >>> expand_list([1,2,[3,4],5,[6,7,8]]) - [[1, 2, 3, 5, 6], [1, 2, 4, 5, 7], [1, 2, 4, 5, 8]] - >>> expand_list([1,2,4,5,6,7,8]) - [[1, 2, 4, 5, 6, 7, 8]] - - """ - list_in_list = [i for i in list_list if type(i) == list].copy() - - try: - nbr_ans_list = max([len(i) for i in list_in_list]) - - ans = [list_list.copy() for i in range(nbr_ans_list)] - for (i,l) in enumerate(ans): - for (j,e) in enumerate(l): - if type(e) == list: - ans[i][j] = e[min(i,len(e)-1)] - # S'il n'y a pas de liste dans la liste (2e exemple) - except ValueError: - ans = [list_list] - - return ans -# ----------------------------- -# Reglages pour 'vim' -# vim:set autoindent expandtab tabstop=4 shiftwidth=4: -# cursor: 16 del diff --git a/pymath/__init__.py b/pymath/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pymath/arithmetic.py b/pymath/arithmetic.py new file mode 100644 index 0000000..98fee2e --- /dev/null +++ b/pymath/arithmetic.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# encoding: utf-8 + + +__all__ = ['gcd'] + +def gcd(a, b): + """Compute gcd(a,b) + + :param a: first number + :param b: second number + :returns: the gcd + + """ + pos_a, _a = (a >= 0), abs(a) + pos_b, _b = (b >= 0), abs(b) + + gcd_sgn = (-1 + 2*(pos_a or pos_b)) + + if _a > _b: + c = _a % _b + else: + c = _b % _a + + if c == 0: + return gcd_sgn * min(_a,_b) + elif _a == 1: + return gcd_sgn * _b + elif _b == 1: + return gcd_sgn * _a + else: + return gcd_sgn * gcd(min(_a,_b), c) + +if __name__ == '__main__': + print(gcd(3, 15)) + print(gcd(3, 15)) + print(gcd(-15, -3)) + print(gcd(-3, -12)) + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del diff --git a/expression.py b/pymath/expression.py similarity index 64% rename from expression.py rename to pymath/expression.py index a6026e4..1e63c53 100644 --- a/expression.py +++ b/pymath/expression.py @@ -1,13 +1,16 @@ #!/usr/bin/env python # encoding: utf-8 -from generic import Stack, flatten_list, expand_list -from fraction import Fraction +from .generic import Stack, flatten_list, expand_list +from .fraction import Fraction +from .renders import txt_render, post2in_fix, tex_render + +__all__ = ['Expression'] class Expression(object): """A calculus expression. Today it can andle only expression with numbers later it will be able to manipulate unknown""" - PRIORITY = {"*" : 3, "/": 4, "+": 2, "-":2, "(": 1} + PRIORITY = {"^": 5, "*" : 3, "/": 4, ":": 3, "+": 2, "-":2, "(": 1} def __init__(self, exp): """ Initiate the expression @@ -25,13 +28,25 @@ class Expression(object): self.feed_fix() # Determine le fix et range la liste dans self.[fix]_tokens + def __str__(self): + """Overload str as it aim to be use in console the render is txt_render""" + return txt_render(self.postfix_tokens) + + 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 + + """ + # TODO: I don't like the name of this method |ven. janv. 17 12:48:14 CET 2014 + return render(self.postfix_tokens) + ## --------------------- ## Mechanism functions 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) + :param render: function which render the list of token (postfix form now) to string """ if not self.can_go_further(): @@ -96,23 +111,52 @@ class Expression(object): ## --------------------- ## String parsing - ## @classmethod ???? + @classmethod def str2tokens(self, exp): """ Parse the expression, ie tranform a string into a list of tokens + /!\ float are not availiable yet! + :param exp: The expression (a string) :returns: list of token """ - tokens = exp.split(" ") + tokens = [''] - for (i,t) in enumerate(tokens): - try: - tokens[i] = int(t) - except ValueError: - pass + for character in exp: + if character.isdigit(): + # for "big" numbers (like 2345) + if type(tokens[-1]) == int: + if tokens[-1] > 0: + tokens[-1] = tokens[-1]*10 + int(character) + else: + tokens[-1] = tokens[-1]*10 - int(character) - return tokens + + # Special case for "-" at the begining of an expression or before "(" + elif tokens[-1] == "-" and \ + str(tokens[-2]) in " (+-*/:": + tokens[-1] = - int(character) + else: + tokens.append(int(character)) + + elif character in "+-*/):^": + tokens.append(character) + + elif character in "(": + # If "3(", ")(" + if self.isNumber(tokens[-1]) \ + or tokens[-1] == ")" : + tokens.append("*") + tokens.append(character) + + elif character == ".": + raise ValueError("No float number please") + + elif character != " ": + raise ValueError("{} is an unvalid character".format(character)) + + return tokens[1:] # --------------------- # "fix" recognition @@ -161,7 +205,7 @@ class Expression(object): return self._infix_tokens elif self._postfix_tokens: - self._infix_tokens = self.post2in_fix(self._postfix_tokens) + self._infix_tokens = post2in_fix(self._postfix_tokens) return self._infix_tokens else: @@ -195,7 +239,7 @@ class Expression(object): # "fix" tranformations @classmethod - def in2post_fix(self, infix_tokens): + 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. @@ -215,9 +259,9 @@ class Expression(object): while topToken != "(": postfixList.append(topToken) topToken = opStack.pop() - elif self.isOperator(token): + 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 (self.PRIORITY[opStack.peek()] >= self.PRIORITY[token]): + while (not opStack.isEmpty()) and (cls.PRIORITY[opStack.peek()] >= cls.PRIORITY[token]): postfixList.append(opStack.pop()) opStack.push(token) else: @@ -228,98 +272,6 @@ class Expression(object): return postfixList - @classmethod - def post2in_fix(self, postfix_tokens): - """ From the postfix_tokens list compute the corresponding infix_tokens list - - @param postfix_tokens: the postfix list of tokens to transform into infix form. - @return: the corresponding infix list of tokens if postfix_tokens. - - >>> Expression.post2in_fix([2, 5, '+', 1, '-', 3, 4, '*', '/']) - ['( ', 2, '+', 5, '-', 1, ' )', '/', '( ', 3, '*', 4, ' )'] - >>> Expression.post2in_fix([2]) - [2] - """ - operandeStack = Stack() - - for token in postfix_tokens: - if self.isOperator(token): - op2 = operandeStack.pop() - - if self.needPar(op2, token, "after"): - op2 = ["( ", op2, " )"] - op1 = operandeStack.pop() - - if self.needPar(op1, token, "before"): - op1 = ["( ", op1, " )"] - res = [op1, token, op2] - - operandeStack.push(res) - - else: - operandeStack.push(token) - - # Manip pour gerer les cas similaires au deuxième exemple - infix_tokens = operandeStack.pop() - if type(infix_tokens) == list: - infix_tokens = flatten_list(infix_tokens) - elif self.isNumber(infix_tokens): - infix_tokens = [infix_tokens] - - return infix_tokens - - # --------------------- - # Tools for placing parenthesis in infix notation - - @classmethod - def needPar(self, operande, operator, posi = "after"): - """Says whether or not the operande needs parenthesis - - :param operande: the operande - :param operator: the operator - :param posi: "after"(default) if the operande will be after the operator, "before" othewise - :returns: bollean - """ - if self.isNumber(operande) and operande < 0: - return 1 - elif not self.isNumber(operande): - # Si c'est une grande expression ou un chiffre négatif - stand_alone = self.get_main_op(operande) - # Si la priorité de l'operande est plus faible que celle de l'opérateur - minor_priority = self.PRIORITY[self.get_main_op(operande)] < self.PRIORITY[operator] - # Si l'opérateur est -/ pour after ou juste / pour before - special = (operator in "-/" and posi == "after") or (operator in "/" and posi == "before") - - return stand_alone and (minor_priority or special) - else: - return 0 - - @classmethod - def get_main_op(self, tokens): - """Getting the main operation of the list of tokens - - :param exp: the list of tokens - :returns: the main operation (+, -, * or /) or 0 if the expression is only one element - - """ - parStack = Stack() - - if len(tokens) == 1: - # Si l'expression n'est qu'un élément - return 0 - - main_op = [] - - for token in tokens: - if token == "(": - parStack.push(token) - elif token == ")": - parStack.pop() - elif self.isOperator(token) and parStack.isEmpty(): - main_op.append(token) - - return min(main_op, key = lambda s: self.PRIORITY[s]) - ## --------------------- ## Computing the expression @@ -333,13 +285,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__", "^": "__pow__"} + return getattr(op1,operations[op])(op2) + ## --------------------- ## Recognize numbers and operators @@ -352,43 +309,44 @@ class Expression(object): :returns: True if the expression can be a number and false otherwise """ - return type(exp) == int or type(exp) == Fraction + return type(exp) == int or \ + type(exp) == Fraction @staticmethod def isOperator(exp): - """Check if the expression is an opération in "+-*/" + """Check if the expression is an opération in "+-*/:^" :param exp: an expression :returns: boolean """ - return (type(exp) == str and exp in "+-*/") + return (type(exp) == str and exp in "+-*/:^") def test(exp): a = Expression(exp) #for i in a.simplify(): - for i in a.simplify(render = render): + #for i in a.simplify(render = txt_render): + for i in a.simplify(render = tex_render): print(i) print("\n") -def render(tokens): - post_tokens = Expression.post2in_fix(tokens) - return ' '.join([str(t) for t in post_tokens]) - if __name__ == '__main__': - exp = "1 + 3 * 5" - test(exp) + #exp = "2 ^ 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) @@ -402,7 +360,7 @@ if __name__ == '__main__': #exp = "2 + 5 * ( 3 - 4 )" #test(exp) - #exp = "( 2 + 5 ) * ( 3 - 4 )" + #exp = "( 2 + 5 ) * ( 3 - 4 )^4" #test(exp) #exp = "( 2 + 5 ) * ( 3 * 4 )" @@ -414,14 +372,27 @@ if __name__ == '__main__': #exp = "( 2 + 5 ) / ( 3 * 4 ) + 1 / 12" #test(exp) - #exp = "( 2 + 5 ) / ( 3 * 4 ) + 1 / 2" + #exp = "( 2+ 5 )/( 3 * 4 ) + 1 / 2" #test(exp) - #exp = "( 2 + 5 ) / ( 3 * 4 ) + 1 / 12 + 5 * 5" + #exp="(-2+5)/(3*4)+1/12+5*5" #test(exp) - exp = "3 / 7 - 2 / 7 * 4 / 3" - test(exp) + #exp="-2*4(12 + 1)(3-12)" + #test(exp) + + + #exp="(-2+5)/(3*4)+1/12+5*5" + #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) + #print(e) + + ## Can't handle it yet!! + #exp="-(-2)" + #test(exp) import doctest doctest.testmod() diff --git a/fraction.py b/pymath/fraction.py similarity index 59% rename from fraction.py rename to pymath/fraction.py index 289df7e..74faa3d 100644 --- a/fraction.py +++ b/pymath/fraction.py @@ -1,7 +1,9 @@ #!/usr/bin/env python # encoding: utf-8 -from arithmetic import gcd +from .arithmetic import gcd + +__all__ = ['Fraction'] class Fraction(object): """Fractions!""" @@ -23,6 +25,11 @@ class Fraction(object): """ steps = [] + if self._num == 0: + steps.append(0) + + return steps + if self._denom < 0: n_frac = Fraction(-self._num, -self._denom) steps.append(n_frac) @@ -50,6 +57,9 @@ class Fraction(object): def __repr__(self): return "< Fraction " + self.__str__() + ">" + + def __float__(self): + return self._num / self._denom def __add__(self, other): if type(other) == Fraction: @@ -70,16 +80,12 @@ class Fraction(object): coef1 = number._denom // gcd_denom coef2 = self._denom // gcd_denom - #steps.append("( {num1} * {coef1} ) / ( {den1} * {coef1} ) + ( {num2} * {coef2} ) / ( {den2} * {coef2} )".format(num1 = self._num, den1 = self._denom, coef1 = coef1, num2 = number._num, den2 = number._denom, coef2 = coef2)) - steps.append([self._num, coef1, "*", self._denom, coef1, "*", '/', number._num, coef2, "*", number._denom, coef2, "*", "/",'+']) com_denom = self._denom * coef1 num1 = self._num * coef1 num2 = number._num * coef2 - #steps.append("( {num1} + {num2} ) / {denom}".format(num1 = num1, num2 = num2, denom = com_denom)) - steps.append([num1, num2, '+', com_denom, '/']) num = num1 + num2 @@ -90,6 +96,42 @@ class Fraction(object): return steps + def __radd__(self, other): + if type(other) == Fraction: + #cool + number = other + else: + number = Fraction(other) + + steps = [] + + if number._denom == self._denom: + com_denom = number._denom + num1 = number._num + num2 = self._num + + else: + gcd_denom = gcd(number._denom, self._denom) + coef1 = self._denom // gcd_denom + coef2 = number._denom // gcd_denom + + steps.append([number._num, coef1, "*", number._denom, coef1, "*", '/', self._num, coef2, "*", self._denom, coef2, "*", "/",'+']) + + com_denom = number._denom * coef1 + num1 = number._num * coef1 + num2 = self._num * coef2 + + steps.append([num1, num2, '+', com_denom, '/']) + + num = num1 + num2 + + ans_frac = Fraction(num, com_denom) + steps.append(ans_frac) + steps += ans_frac.simplify() + + return steps + + def __sub__(self, other): if type(other) == Fraction: #cool @@ -109,14 +151,12 @@ class Fraction(object): coef1 = number._denom // gcd_denom coef2 = self._denom // gcd_denom - #steps.append("( {num1} * {coef1} ) / ( {den1} * {coef1} ) - ( {num2} * {coef2} ) / ( {den2} * {coef2} )".format(num1 = self._num, den1 = self._denom, coef1 = coef1, num2 = number._num, den2 = number._denom, coef2 = coef2)) steps.append([self._num, coef1, "*", self._denom, coef1, "*", '/', number._num, coef2, "*", number._denom, coef2, "*", "/",'-']) com_denom = self._denom * coef1 num1 = self._num * coef1 num2 = number._num * coef2 - #steps.append("( {num1} - {num2} ) / {denom}".format(num1 = num1, num2 = num2, denom = com_denom)) steps.append([num1, num2, '-', com_denom, '/']) num = num1 - num2 @@ -126,6 +166,44 @@ class Fraction(object): steps += ans_frac.simplify() return steps + + def __rsub__(self, other): + if type(other) == Fraction: + #cool + number = other + else: + number = Fraction(other) + + steps = [] + + if number._denom == self._denom: + com_denom = number._denom + num1 = number._num + num2 = self._num + + else: + gcd_denom = gcd(number._denom, self._denom) + coef1 = self._denom // gcd_denom + coef2 = number._denom // gcd_denom + + steps.append([number._num, coef1, "*", number._denom, coef1, "*", '/', self._num, coef2, "*", self._denom, coef2, "*", "/",'-']) + + com_denom = number._denom * coef1 + num1 = number._num * coef1 + num2 = self._num * coef2 + + steps.append([num1, num2, '-', com_denom, '/']) + + num = num1 - num2 + + ans_frac = Fraction(num, com_denom) + steps.append(ans_frac) + steps += ans_frac.simplify() + + return steps + + def __neg__(self): + return [Fraction(-self._num,self._denom)] def __mul__(self, other): if type(other) == Fraction: @@ -135,7 +213,6 @@ class Fraction(object): number = Fraction(other) steps = [] - #steps.append("( {num1} * {num2} ) / ( {denom1} * {denom2} )".format(num1 = self._num, num2 = number._num, denom1 = self._denom, denom2 = number._denom)) steps.append([self._num, number._num, '*', self._denom, number._denom, '*', '/']) @@ -148,6 +225,26 @@ class Fraction(object): return steps + def __rmul__(self, other): + if type(other) == Fraction: + #cool + number = other + else: + number = Fraction(other) + + steps = [] + + steps.append([number._num, self._num, '*', number._denom, self._denom, '*', '/']) + + num = self._num * number._num + denom = self._denom * number._denom + + ans_frac = Fraction(num, denom) + steps.append(ans_frac) + steps += ans_frac.simplify() + + return steps + def __truediv__(self, other): if type(other) == Fraction: #cool @@ -157,17 +254,46 @@ class Fraction(object): steps = [] number = Fraction(number._denom, number._num) + steps.append([self, number, "/"]) steps += self * number return steps + def __rtruediv__(self, other): + if type(other) == Fraction: + #cool + number = other + else: + number = Fraction(other) + + steps = [] + self_inv = Fraction(self._denom, self._num) + steps.append([number, self_inv, "/"]) + steps += number * self_inv + + return steps + + def __abs__(self): + return Fraction(abs(self._num), abs(self._denom)) + + def __eq__(self, other): + """ == """ + if type(other) == Fraction: + number = other + else: + number = Fraction(other) + + return self._num * number._denom == self._denom * number._num + def __lt__(self, other): + """ < """ if type(other) == Fraction: return (self._num / self._denom) < (other._num / other._denom) else: return (self._num / self._denom) < other def __le__(self, other): + """ <= """ if type(other) == Fraction: return (self._num / self._denom) <= (other._num / other._denom) else: @@ -178,17 +304,17 @@ class Fraction(object): if __name__ == '__main__': f = Fraction(1, 12) g = Fraction(1, 12) - h = Fraction(-1,5) - t = Fraction(-4,5) + h = Fraction(1,-5) + t = Fraction(4,5) print("---------") - for i in (f - 1): + for i in (1 + h): print(i) print("---------") - for i in (f + 1): - print(i) - print("---------") - for i in (f + g): - print(i) + #for i in (f + t): + # print(i) + #print("---------") + #for i in (f + g): + # print(i) #print("---------") #for i in (f - g): # print(i) diff --git a/pymath/generic.py b/pymath/generic.py new file mode 100644 index 0000000..f1a43ef --- /dev/null +++ b/pymath/generic.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python +# encoding: utf-8 + + +class Stack(object): + """Docstring for Stack """ + + def __init__(self): + """@todo: to be defined1 """ + self.items = [] + + def pushFromList(self, list): + """Push the list in the stack + + :param list: a list + """ + for i in list[::-1]: + self.push(i) + + def isEmpty(self): + """ Says if the stack is empty + :returns: @todo + + """ + return self.items == [] + + def push(self, item): + """Push an item in the stack + + :param item: @todo + :returns: @todo + + """ + self.items.append(item) + + def pop(self): + """Getting the last item and remove it + :returns: last item + + """ + return self.items.pop() + + def peek(self, posi = 0): + """Getting the last item + :param posi: which item to peek 0 (last) 1 (the onebefore the last)... + :returns: the item + + """ + return self.items[-1 - posi] + + def __len__(self): + return len(self.items) + + def __str__(self): + return str(self.items) + " -> " + + def __add__(self, addList): + return self.items + addList + + +def flatten_list(a, result=None): + """Flattens a nested list. + + >>> flatten_list([ [1, 2, [3, 4] ], [5, 6], 7]) + [1, 2, 3, 4, 5, 6, 7] + """ + if result is None: + result = [] + + for x in a: + if isinstance(x, list): + flatten_list(x, result) + else: + result.append(x) + + return result + +def first_elem(ll): + """Get the first element in imbricates lists + # TODO: Fonction pourrie mais j'ai pas le temps de faire mieux! |mar. janv. 28 22:32:22 CET 2014 + + :param list: list of lists of lists... + :returns: the first element + + >>> first_elem(1) + 1 + >>> first_elem([1,2]) + 1 + >>> first_elem([["abc"]]) + 'a' + >>> first_elem("abc") + 'a' + >>> first_elem([[[1,2],[3,4]], [5,6]]) + 1 + >>> first_elem([[["ab",2],[3,4]], [5,6]]) + 'a' + + """ + if hasattr(ll, '__contains__'): + if len(ll) == 1 and type(ll) == str: + return ll[0] + else: + return first_elem(ll[0]) + else: + return ll + +def last_elem(ll): + """Get the last element in imbricates lists + # TODO: Fonction pourrie mais j'ai pas le temps de faire mieux! |mar. janv. 28 22:32:22 CET 2014 + + :param list: list of lists of lists... + :returns: the last element + + >>> last_elem(1) + 1 + >>> last_elem([1,2]) + 2 + >>> last_elem([["abc"]]) + 'c' + >>> last_elem("abc") + 'c' + >>> last_elem([[[1,2],[3,4]], [5,6]]) + 6 + >>> last_elem([[["ab",2],[3,4]], [5,6]]) + 6 + + """ + if hasattr(ll, '__contains__'): + if len(ll) == 1 and type(ll) == str: + return ll[-1] + else: + return last_elem(ll[-1]) + else: + return ll + + +def expand_list(list_list): + """Expand list of list + + >>> expand_list([1,2,[3,4],5,[6,7,8]]) + [[1, 2, 3, 5, 6], [1, 2, 4, 5, 7], [1, 2, 4, 5, 8]] + >>> expand_list([1,2,4,5,6,7,8]) + [[1, 2, 4, 5, 6, 7, 8]] + + """ + list_in_list = [i for i in list_list if type(i) == list].copy() + + try: + nbr_ans_list = max([len(i) for i in list_in_list]) + + ans = [list_list.copy() for i in range(nbr_ans_list)] + for (i,l) in enumerate(ans): + for (j,e) in enumerate(l): + if type(e) == list: + ans[i][j] = e[min(i,len(e)-1)] + # S'il n'y a pas de liste dans la liste (2e exemple) + except ValueError: + 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 + +def convolution_dict(D1, D2, op = lambda x,y:x*y,\ + op_key = lambda x,y: x + y, \ + commutative = True, op_twice = lambda x,y: x + y): + """Convolution of two dictionaries + + :param D1: First dictionary + :param D2: Second dictionary + :param op: Operation of perform in value + :param commutative: keys are commutative? + :param op_twice: operation on value if the key appear twice + + >>> convolution_dict({"a": 1, "b":3}, {"a":2, "":4}) == {"aa":2, "a": 4, "ba":6, "b":12} + True + >>> convolution_dict({"a": 1, "b":3}, {"a":2, "b":4}) == {"aa":2, "ab":10, "bb":12} + True + >>> convolution_dict({"a": 1, "b":3}, {"a":2, "b":4}, commutative = False) == {"aa":2, "ab":10, "bb":12} + False + >>> convolution_dict({"a": 1, "b":3}, {"a":2, "b":4}, commutative = False) == {"aa":2, "ab":4,"ba":6, "bb":12} + True + >>> convolution_dict({"a": 1, "b":3}, {"a":2, "b":4}, \ + op_twice = lambda x,y:[x,y]) == {"aa":2, "ab":[4,6], "bb":12} + True + + """ + new_dict = {} + + for k1 in sorted(D1.keys()): + for k2 in sorted(D2.keys()): + if op_key(k1,k2) in new_dict.keys(): + key = op_key(k1,k2) + new_dict[key] = op_twice(new_dict[key], op(D1[k1],D2[k2])) + + elif op_key(k2,k1) in new_dict.keys() and commutative: + key = op_key(k2,k1) + new_dict[key] = op_twice(new_dict[key], op(D1[k1],D2[k2])) + + else: + key = op_key(k1,k2) + new_dict[key] = op(D1[k1],D2[k2]) + + return new_dict + +if __name__ == '__main__': + import doctest + doctest.testmod() + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del diff --git a/pymath/random_expression.py b/pymath/random_expression.py new file mode 100644 index 0000000..ba209bd --- /dev/null +++ b/pymath/random_expression.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python +# encoding: utf-8 + +from random import randint +from .expression import Expression +from .renders import tex_render, txt_render +import re + +from .arithmetic import gcd + +class RdExpression(object): + """A generator of random expression builder""" + + def __init__(self, form, conditions = [], with_Exp = True): + """Initiate the generator + + :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 with_Exp: If True, __call__ return an expression rendered through Expression class. If False, keep form and replace inside. + + """ + self._form = form + self._conditions = conditions + self._with_Exp = with_Exp + self._letters = self.get_letters() + self._gene_varia = {} + self._gene_2replaced= {} + + def get_2replaced(self): + """Get elements of self._form which will have to be replaced + :returns: set for elements which have to be replaced + + """ + pattern = "\{(.*?)\}" #select inside {} non greedy way + + varia_form = re.findall(pattern, self._form) + varia_form = set(varia_form) + + varia_cond = set() + for c in self._conditions: + varia_cond = varia_cond | set(re.findall(pattern, c)) + + self._2replaced = varia_cond | varia_form + + return self._2replaced + + def get_letters(self): + """Find letters in the form + :returns: list of letters + + """ + v2replaced = self.get_2replaced() + varia = set() + + pattern = "([a-zA-Z]+)" + for v in v2replaced: + lvar = set(re.findall(pattern, v)) + varia = varia | lvar + + return varia + + + def __call__(self, val_min = -10, val_max = 10, render = tex_render): + """RdExpression once it is initiate act like a function which create random expressions. + + :param val_min: minimum value random generation + :param val_max: maximum value random generation + :param render: Render of the expression (returns an Expression by default) + :returns: an formated random expression + + """ + if self._with_Exp: + return render(self.raw_exp(val_min, val_max).postfix_tokens) + else: + return self.raw_str(val_min, val_max) + + def raw_str(self, val_min = -10, val_max = 10): + """Return raw string (don't use Expression for rendering or parsing) + + :param val_min: minimum value random generation + :param val_max: maximum value random generation + :returns: an random Expression object + + """ + self.gene_varia(val_min, val_max) + + while not(self.val_conditions()): + self.gene_varia(val_min, val_max) + + exp = self._form.format(**self._gene_2replaced) + + return exp + + def raw_exp(self, val_min = -10, val_max = 10): + """Same as raw_str but returns an Expression object + + :param val_min: minimum value random generation + :param val_max: maximum value random generation + :returns: an random Expression object + + """ + exp = self.raw_str(val_min, val_max) + + return Expression(exp) + + def gene_varia(self, val_min = -10, val_max = 10): + """Randomly generates variables/letters + + Varia can't be equal to 0 + + """ + for l in self._letters: + self._gene_varia[l] = randint(val_min, val_max) + while self._gene_varia[l] == 0: + self._gene_varia[l] = randint(val_min, val_max) + + + for e in self._2replaced: + self._gene_2replaced[e] = eval(e, globals(), self._gene_varia) + + def val_conditions(self): + """Tells whether or not conditions are validates + :returns: boolean + + """ + if self._conditions != []: + return eval(" and ".join(self._conditions).format(**self._gene_2replaced)) + else: + return True + +def desc_rdExp(rdExp): + print("--------------------") + print("form: ",rdExp._form) + print("Conditions: ",rdExp._conditions) + print("Letters: ", rdExp._letters) + print("2replaced: ", rdExp._2replaced) + print("Call : ", rdExp(render = tex_render)) + print("Gene varia: ", rdExp._gene_varia) + print("Gene 2replaced: ", rdExp._gene_2replaced) + print('') + + +if __name__ == '__main__': + form = "{a}*-14 / (2*{b}) : -23 / 4" + cond = ["{a} + {b} in [1, 2, 3, 4, 5]", "{a} not in [1]", "{b} not in [1]"] + rdExp1 = RdExpression(form, cond) + desc_rdExp(rdExp1) + rdExp2 = RdExpression(form) + desc_rdExp(rdExp2) + + form = "{a+a*10}*4 + {a} + 2*{b}" + cond = ["{a} + {b} in [1, 2, 3, 4, 5]", "abs({a}) not in [1]", "{b} not in [1]", "gcd({a},{b}) == 1"] + rdExp3 = RdExpression(form, cond) + desc_rdExp(rdExp3) + + form = "{a+a*10}*4 + {a} + 2*{b}" + cond = ["{a-b} + {b} in list(range(20))", "abs({a}) not in [1]", "{b} not in [1]", "gcd({a},{b}) == 1"] + rdExp3 = RdExpression(form, cond) + desc_rdExp(rdExp3) + + + + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del diff --git a/pymath/render.py b/pymath/render.py new file mode 100644 index 0000000..24c36e8 --- /dev/null +++ b/pymath/render.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python +# encoding: utf-8 + +from .generic import Stack,flatten_list +from .fraction import Fraction + +__all__ = ['Render'] + +class Render(object): + """A class which aims to create render functions from three dictionnaries: + - op_infix: dict of caracters or two argument functions + - op_postfix: dict of 2 arguments functions + - other: dict of caracters + Those three dictionnaries while define how a postfix expression will be transform into a string. + """ + + PRIORITY = {"^": 4,"*" : 3, "/": 3, ":": 3, "+": 2, "-":2, "(": 1} + + def __init__(self, op_infix = {}, op_postfix = {}, other = {}, join = " ", type_render = {str: str, int: str, Fraction: str}): + """Initiate the render + + @param op_infix: the dictionnary of infix operator with how they have to be render + @param op_postfix: the dictionnary of postfix operator with how they have to be render + @param other: other caracters like parenthesis. + @param raw: the caracter for joining the list of tokens (if False then it returns the list of tokens) + @param type_render: how to render number (str or tex for fractions for example) + """ + + self.op_infix = op_infix + self.op_postfix = op_postfix + self.other = other + # TODO: there may be issues with PRIORITY if a sign does not appear in PRIORITY + + self.join = join + self.type_render = type_render + + self.operators = list(self.op_infix.keys()) + list(self.op_postfix.keys()) + list(self.other.keys()) + + def __call__(self, postfix_tokens): + """Make the object acting like a function + + :param postfix_tokens: the list of postfix tokens to be render + :returns: the render string + + """ + operandeStack = Stack() + + + for token in postfix_tokens: + if self.isOperator(token): + + op2 = operandeStack.pop() + if self.needPar(op2, token, "after"): + op2 = [self.other["("] , op2 , self.other[")"]] + + op1 = operandeStack.pop() + if self.needPar(op1, token, "before"): + op1 = [self.other["("] , op1 , self.other[")"]] + + if token in self.op_infix: + if type(self.op_infix[token]) == str: + res = flist([op1 , self.op_infix[token] , op2]) + else: + res = flist([self.op_infix[token](op1, op2)]) + + + elif token in self.op_postfix: + res = flist([self.op_postfix[token](op1, op2)]) + + # Trick to remember the main op when the render will be done! + res.mainOp = token + + operandeStack.push(res) + + 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.isNumerande(infix_tokens): + infix_tokens = [infix_tokens] + + if self.join: + return self.join.join(flatten_list([self.render_from_type(t) for t in infix_tokens])) + else: + return infix_tokens + + def render_from_type(self, op): + """ 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.isNumerande(op): + return self.type_render[type(op)](op) + else: + return op + + + # --------------------- + # Tools for placing parenthesis in infix notation + + def needPar(self, operande, operator, posi = "after"): + """Says whether or not the operande needs parenthesis + + :param operande: the operande + :param operator: the operator + :param posi: "after"(default) if the operande will be after the operator, "before" othewise + :returns: bollean + """ + # Si l'operande est negatif + if self.isNumber(operande) \ + and operande < 0 \ + and posi == "after": + return 1 + + # Pas de parenthèses si c'est une lettre ou une fraction + elif (type(operande) == str and operande.isalpha()) \ + or type(operande) == Fraction: + return 0 + + elif not self.isNumber(operande): + # Si c'est une grande expression + stand_alone = self.get_main_op(operande) + # Si la priorité de l'operande est plus faible que celle de l'opérateur + minor_priority = self.PRIORITY[self.get_main_op(operande)] < self.PRIORITY[operator] + # Si l'opérateur est - ou / pour after ou / ou ^ pour before + special = (operator in "-/" and posi == "after") or (operator in "/^" and posi == "before") + + return stand_alone and (minor_priority or special) + else: + return 0 + + def get_main_op(self, tokens): + """Getting the main operation of the list of tokens + + :param exp: the list of tokens + :returns: the main operation (+, -, * or /) or 0 if the expression is only one element + + """ + if hasattr(tokens, "mainOp"): + return tokens.mainOp + + if len(tokens) == 1: + # Si l'expression n'est qu'un élément + return 0 + + parStack = Stack() + main_op = [] + + for token in tokens: + if token == "(": + parStack.push(token) + elif token == ")": + parStack.pop() + elif self.isOperator(token) and parStack.isEmpty(): + main_op.append(token) + + return min(main_op, key = lambda s: self.PRIORITY[s]) + + ## --------------------- + ## Recognize numbers and operators + + @staticmethod + def isNumber( exp): + """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 + #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 + + def isOperator(self, exp): + """Check if the expression is in self.operators + + :param exp: an expression + :returns: boolean + + """ + return (type(exp) == str and exp in self.operators) + +class flist(list): + """Fake list- they are used to stock the main operation of an rendered expression""" + pass + + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del diff --git a/pymath/renders.py b/pymath/renders.py new file mode 100644 index 0000000..1db9114 --- /dev/null +++ b/pymath/renders.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +# encoding: utf-8 + +from .render import Render +from .fraction import Fraction +from .generic import first_elem, last_elem + +__all__ = ['post2in_fix', 'tex_render', 'txt_render'] + +# ------------------------ +# A infix to postfix list convertor + +p2i_infix = {"+": "+", "-": "-", "*": "*", "/" : "/", ":": ":", "^":"^"} +p2i_postfix = {} +p2i_other = {"(": "(", ")": ")"} + +post2in_fix = Render(p2i_infix, p2i_postfix, p2i_other, join = False) + +# ------------------------ +# A console render + +def txtMult(op1,op2): + """ Tex render for * + Cases where \\times won't be displayed + * nbr letter + * nbr ( + * )( + """ + first_nbr = type(op1) in [int, Fraction] + seg_letter = type(op2) == str and op2.isalpha() + first_par = (first_elem(op2) == "(") + seg_par = (last_elem(op1) == ")") + + if (first_nbr and (seg_letter or seg_par)) \ + or (first_par and seg_par): + return [op1, op2] + else: + return [op1, "*", op2] + +txt_infix = {"+": "+", "-": "-", "*": txtMult, "/" : "/", ":":":", "^":"^"} +txt_postfix = {} +txt_other = {"(": "(", ")": ")"} + +txt_render = Render(txt_infix, txt_postfix, txt_other) + +# ------------------------ +# A latex render + +def texSlash(op1, op2): + """ Tex render for / """ + if not Render.isNumerande(op1) and op1[0] == "(" and op1[-1] == ")": + op1 = op1[1:-1] + if not Render.isNumerande(op2) and op2[0] == "(" and op2[-1] == ")": + op2 = op2[1:-1] + return ["\\frac{" , op1 , "}{" , op2 , "}"] + +def texFrac(frac): + """ Tex render for Fractions""" + return ["\\frac{" , str(frac._num) , "}{" , str(frac._denom) , "}"] + +def texMult(op1,op2): + """ Tex render for * + Cases where \\times won't be displayed + * nbr letter + * nbr ( + * )( + """ + first_nbr = type(op1) in [int, Fraction] + seg_letter = type(op2) == str and op2.isalpha() + first_par = (first_elem(op2) == "(") + seg_par = (last_elem(op1) == ")") + + if (first_nbr and (seg_letter or seg_par)) \ + or (first_par and seg_par): + return [op1, op2] + else: + return [op1, "\\times", op2] + +tex_infix = {"+": " + ", "-": " - ", "*": texMult , ":": ":", "^":"^"} +tex_postfix = {"/": texSlash} +tex_other = {"(": "(", ")": ")"} +tex_type_render = {str:str, int: str, Fraction: texFrac} + +tex_render = Render(tex_infix, tex_postfix, tex_other, type_render = tex_type_render) + + + +if __name__ == '__main__': + #exp = [2, 5, '^', 1, '-', 3, 4, '*', ':'] + #print(txt_render(exp)) + #exp = [2, 5, '^', 1, '-', 3, 4, '*', '/', 3, 5, '/', ':'] + exp = [2, -3, "*"] + print(tex_render(exp)) + #exp = [2, 5, '^', 1, '-', 3, 4, '*', '/', 3, '+'] + #print(post2in_fix(exp)) + + + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..e4fca94 --- /dev/null +++ b/setup.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +from distutils.core import setup + +setup(name='pyMath', + version='0.1dev', + description='Computing like a student', + author='Benjamin Bertrand', + author_email='lafrite@poneyworld.net', + packages=['pymath'], + ) diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_arithmetic.py b/test/test_arithmetic.py new file mode 100644 index 0000000..f5cb2d6 --- /dev/null +++ b/test/test_arithmetic.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# encoding: utf-8 + + +import unittest + +from pymath import arithmetic + + + +class TestArithmetic(unittest.TestCase): + """Testing functions from pymath.arithmetic""" + + def test_gcd_commu(self): + self.assertEqual(arithmetic.gcd(3, 15), arithmetic.gcd(15,3)) + + def test_gcd1(self): + self.assertEqual(arithmetic.gcd(3, 15), 3) + + def test_gcd2(self): + self.assertEqual(arithmetic.gcd(14, 21), 7) + + def test_gcd_prem(self): + self.assertEqual(arithmetic.gcd(14, 19), 1) + + def test_gcd_neg(self): + self.assertEqual(arithmetic.gcd(3, -15), 3) + self.assertEqual(arithmetic.gcd(-3, -15), -3) + + +if __name__ == '__main__': + unittest.main() + + + + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del + diff --git a/test/test_expression.py b/test/test_expression.py new file mode 100644 index 0000000..d079e77 --- /dev/null +++ b/test/test_expression.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +# encoding: utf-8 + + + +import unittest + +from pymath.expression import Expression +from pymath.fraction import Fraction +from pymath.generic import first_elem +from pymath.renders import txt_render + + +class TestExpression(unittest.TestCase): + """Testing functions from pymath.expression""" + + def test_init_from_str(self): + exp = Expression("2 + 3") + self.assertEqual(exp.infix_tokens, [2, "+", 3]) + self.assertEqual(exp.postfix_tokens, [2, 3, "+"]) + + def test_init_from_exp(self): + pass + + def test_infix_tokens(self): + pass + + def test_postfix_tokens(self): + pass + + def test_str2tokens_big_num(self): + exp = "123 + 3" + tok = Expression.str2tokens(exp) + self.assertEqual(tok, [123, "+", 3]) + + def test_str2tokens_beg_minus(self): + exp = "-123 + 3" + tok = Expression.str2tokens(exp) + self.assertEqual(tok, [-123, "+", 3]) + + def test_str2tokens_time_lack(self): + exp = "(-3)(2)" + tok = Expression.str2tokens(exp) + self.assertEqual(tok, ["(", -3, ")", "*","(", 2, ")" ]) + + def test_str2tokens_time_lack2(self): + exp = "-3(2)" + tok = Expression.str2tokens(exp) + self.assertEqual(tok, [-3, "*","(", 2, ")" ]) + + def test_str2tokens_error_float(self): + exp = "1 + 1.3" + self.assertRaises(ValueError, Expression.str2tokens, exp) + + def test_str2tokens_error(self): + 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 + + def test_isOperator(self): + pass + + def test_simplify_frac(self): + exp = Expression("1/2 - 4") + steps = ["[1, 2, '/', 4, '-']", \ + "[< Fraction 1 / 2>, 4, '-']", \ + "[1, 1, '*', 2, 1, '*', '/', 4, 2, '*', 1, 2, '*', '/', '-']", \ + "[1, 8, '-', 2, '/']", \ + '[< Fraction -7 / 2>]'] + self.assertEqual(steps, list(exp.simplify())) + + +if __name__ == '__main__': + unittest.main() + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del diff --git a/test/test_fraction.py b/test/test_fraction.py new file mode 100644 index 0000000..05963a1 --- /dev/null +++ b/test/test_fraction.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +# encoding: utf-8 + +import unittest + +from pymath.fraction import Fraction + +class TestFraction(unittest.TestCase): + """Testing functions from pymath.Fraction""" + + def setUp(self): + self.listFrom = [Fraction(1,3), 1] + self.listAgainst = [ Fraction(1,3), \ + Fraction(2,3), \ + Fraction(4,5), \ + Fraction(-1, 3), \ + Fraction(1,-3), \ + 1, + ] + + def test_add(self): + ans = [[Fraction(2, 3), 1, Fraction(17, 15), 0, 0, Fraction(4,3)], \ + [Fraction(4,3), Fraction(5,3), Fraction(9,5), Fraction(2,3), Fraction(2,3), 2] \ + ] + # TODO: Bug pour 1 + 1/-3 |sam. févr. 22 07:01:29 CET 2014 + + for (i, f1) in enumerate(self.listFrom): + for (j, f2) in enumerate(self.listAgainst): + res = f1 + f2 + + #print("-----------") + #print("f1 : ", f1) + #print("f2 : ", f2) + #print(res) + + # On est obligé de faire ça pour gérer le cas de 1+1 qui ne passe pas par la classe Fraction + if type(res) == list: + self.assertEqual(res[-1], ans[i][j]) + else: + self.assertEqual(res, ans[i][j]) + + def test_sub(self): + ans = [[0, Fraction(-1,3), Fraction(-7, 15), Fraction(2,3), Fraction(2,3), Fraction(-2,3)], \ + [Fraction(2,3), Fraction(1,3), Fraction(1,5), Fraction(4,3), Fraction(4,3), 0] \ + ] + # TODO: bug pour 1 - 1/-3 |sam. févr. 22 07:05:15 CET 2014 + + for (i, f1) in enumerate(self.listFrom): + for (j, f2) in enumerate(self.listAgainst): + res = f1 - f2 + + #print("-----------") + #print("f1 : ", f1) + #print("f2 : ", f2) + #print(res) + + # On est obligé de faire ça pour gérer le cas de 1-1 qui ne passe pas par la classe Fraction + if type(res) == list: + self.assertEqual(res[-1], ans[i][j]) + else: + self.assertEqual(res, ans[i][j]) + + def test_neg(self): + pass + + def test_mul(self): + ans = [[Fraction(1, 9), Fraction(2,9), Fraction(4, 15), Fraction(-1,9), Fraction(-1,9), Fraction(1,3)], \ + [ Fraction(1,3), Fraction(2,3), Fraction(4,5), Fraction(-1, 3), Fraction(1,-3), 1] \ + ] + + for (i, f1) in enumerate(self.listFrom): + for (j, f2) in enumerate(self.listAgainst): + res = f1 * f2 + + #print("-----------") + #print("f1 : ", f1) + #print("f2 : ", f2) + #print(res) + + # On est obligé de faire ça pour gérer le cas de 1*1 qui ne passe pas par la classe Fraction + if type(res) == list: + self.assertEqual(res[-1], ans[i][j]) + else: + self.assertEqual(res, ans[i][j]) + + def test_truediv(self): + ans = [[1, Fraction(1,2), Fraction(5, 12), -1, -1, Fraction(1,3)], \ + [3, Fraction(3,2), Fraction(5,4), -3, -3, 1] \ + ] + + for (i, f1) in enumerate(self.listFrom): + for (j, f2) in enumerate(self.listAgainst): + res = f1 / f2 + + #print("-----------") + #print("f1 : ", f1) + #print("f2 : ", f2) + #print(res) + + # On est obligé de faire ça pour gérer le cas de 1/1 qui ne passe pas par la classe Fraction + if type(res) == list: + self.assertEqual(res[-1], ans[i][j]) + else: + self.assertEqual(res, ans[i][j]) + + def test_lt(self): + pass + + def test_le(self): + pass + + +if __name__ == '__main__': + unittest.main() + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del diff --git a/test/test_generic.py b/test/test_generic.py new file mode 100644 index 0000000..7f9c5b2 --- /dev/null +++ b/test/test_generic.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# encoding: utf-8 + + +import unittest + +from pymath import generic + +class TestGeneric(unittest.TestCase): + """Testing functions from pymath.generic""" + + def test_flatten_list1(self): + l = [1, [2,3], [[4,5], 6], 7] + flat_l = generic.flatten_list(l) + + true_flat = list(range(1,8)) + + self.assertEqual(flat_l, true_flat) + + def test_flatten_list2(self): + l = list(range(10)) + flat_l = generic.flatten_list(l) + + true_flat = list(range(10)) + + self.assertEqual(flat_l, true_flat) + + def test_first_elem_simple_iter(self): + """ For simple iterable """ + l = range(10) + first = generic.first_elem(l) + + self.assertAlmostEqual(0,first) + + s = "plopplop" + first = generic.first_elem(s) + self.assertAlmostEqual("p", first) + + def test_first_elem_iter_in_iter(self): + """ Interable in iterable """ + l = [[1,2],[4, 5, [6,7,8]], 9] + first = generic.first_elem(l) + + self.assertAlmostEqual(first, 1) + + l = [[[1]]] + first = generic.first_elem(l) + + self.assertAlmostEqual(first, 1) + + l = ["abc"] + first = generic.first_elem(l) + + self.assertAlmostEqual(first, "a") + + l = ["abc",[4, 5, [6,7,8]], 9] + first = generic.first_elem(l) + + self.assertAlmostEqual(first, "a") + + l = [["abc",1],[4, 5, [6,7,8]], 9] + first = generic.first_elem(l) + + self.assertAlmostEqual(first, "a") + +if __name__ == '__main__': + unittest.main() + + + + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del diff --git a/test/test_random_expression.py b/test/test_random_expression.py new file mode 100644 index 0000000..f69a627 --- /dev/null +++ b/test/test_random_expression.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +# encoding: utf-8 + + +import unittest + +from pymath.random_expression import RdExpression + + +class TestRandomExpression(unittest.TestCase): + """Testing functions from pymath.random_expression""" + + def test_only_form(self): + form = "{a} + 2" + rdExp = RdExpression(form) + + self.assertEqual(rdExp._letters, {'a'}) + self.assertEqual(rdExp._2replaced, {'a'}) + + rdExp() + self.assertEqual(set(rdExp._gene_varia.keys()), {'a'}) + self.assertEqual(set(rdExp._gene_2replaced.keys()), {'a'}) + + def test_only_form_calc(self): + form = "{a + b} + 2" + rdExp = RdExpression(form) + + self.assertEqual(rdExp._letters, {'a', 'b'}) + self.assertEqual(rdExp._2replaced, {'a + b'}) + + rdExp() + self.assertEqual(set(rdExp._gene_varia.keys()), {'a', 'b'}) + self.assertEqual(set(rdExp._gene_2replaced.keys()), {'a + b'}) + + def test_only_form_cond(self): + form = "{a} + 2" + cond = ["{a} == 3"] + rdExp = RdExpression(form, cond) + + self.assertEqual(rdExp._letters, {'a'}) + self.assertEqual(rdExp._2replaced, {'a'}) + + rdExp() + self.assertEqual(set(rdExp._gene_varia.keys()), {'a'}) + self.assertEqual(set(rdExp._gene_2replaced.keys()), {'a'}) + + self.assertEqual(rdExp._gene_varia['a'], 3) + + def test_only_form_conds(self): + form = "{a} + 2" + cond = ["{a} in list(range(5))", "{a} % 2 == 1"] + rdExp = RdExpression(form, cond) + + self.assertEqual(rdExp._letters, {'a'}) + self.assertEqual(rdExp._2replaced, {'a'}) + + rdExp() + self.assertEqual(set(rdExp._gene_varia.keys()), {'a'}) + self.assertEqual(set(rdExp._gene_2replaced.keys()), {'a'}) + + self.assertTrue(rdExp._gene_varia['a'] in list(range(5))) + self.assertTrue(rdExp._gene_varia['a'] % 2 == 1) + + def test_only_form_calc_cond(self): + form = "{a*3} * {b}" + cond = ["{a} == 3"] + rdExp = RdExpression(form, cond) + + self.assertEqual(rdExp._letters, {'a', 'b'}) + self.assertEqual(rdExp._2replaced, {'a', 'b', 'a*3'}) + + rdExp() + self.assertEqual(set(rdExp._gene_varia.keys()), {'a', 'b'}) + self.assertEqual(set(rdExp._gene_2replaced.keys()), {'a', 'b', 'a*3'}) + + self.assertEqual(rdExp._gene_varia['a'], 3) + + + def test_only_form_calc_cond_calc(self): + form = "{a} + 2" + + pass + + + +if __name__ == '__main__': + unittest.main() + + + + + + + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del + diff --git a/test/test_renders.py b/test/test_renders.py new file mode 100644 index 0000000..87da4f6 --- /dev/null +++ b/test/test_renders.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# encoding: utf-8 + + +import unittest + +from pymath.renders import tex_render, txt_render +from pymath.fraction import Fraction + + + +class TestTexRender(unittest.TestCase): + """Testing functions from pymath.renders.tex_render""" + + def test_type_render_int(self): + self.assertEqual(tex_render([2]), "2") + + def test_type_render_str(self): + self.assertEqual(tex_render(["a"]), "a") + + def test_type_render_fraction(self): + self.assertEqual(tex_render([Fraction(1,2)]), "\\frac{ 1 }{ 2 }") + + def test_mult_interger(self): + exps = [ [2, 3, "*"], [2, -3, "*"], [-2, 3, "*"]] + wanted_render = [ "2 \\times 3", "2 \\times ( -3 )", "-2 \\times 3"] + for (i,e) in enumerate(exps): + rend = tex_render(e) + self.assertEqual(rend, wanted_render[i]) + + def test_mult_letter(self): + exps = [ [2, "a", "*"], ["a", 3, "*"], [-2, "a", "*"], ["a", -2, "*"]] + wanted_render = [ "2 a", "a \\times 3", "-2 a", "a \\times ( -2 )"] + for (i,e) in enumerate(exps): + rend = tex_render(e) + self.assertEqual(rend, wanted_render[i]) + + def test_mult_fraction(self): + exps = [ [2, Fraction(1,2), "*"], [Fraction(1,2), 3, "*"]] + wanted_render = [ "2 \\times \\frac{ 1 }{ 2 }", "\\frac{ 1 }{ 2 } \\times 3"] + for (i,e) in enumerate(exps): + rend = tex_render(e) + self.assertEqual(rend, wanted_render[i]) + + def test_mult_exp(self): + pass + + def test_slash(self): + pass + + + + +class TesttxtRender(unittest.TestCase): + """Testing functions from pymath.renders.txt_render""" + + def test_type_render_int(self): + self.assertEqual(txt_render([2]), "2") + + def test_type_render_str(self): + self.assertEqual(txt_render(["a"]), "a") + + def test_type_render_fraction(self): + self.assertEqual(txt_render([Fraction(1,2)]), "1 / 2") + + def test_mult_interger(self): + exps = [ [2, 3, "*"], [2, -3, "*"], [-2, 3, "*"]] + wanted_render = [ "2 * 3", "2 * ( -3 )", "-2 * 3"] + for (i,e) in enumerate(exps): + rend = txt_render(e) + self.assertEqual(rend, wanted_render[i]) + + def test_mult_letter(self): + exps = [ [2, "a", "*"], ["a", 3, "*"], [-2, "a", "*"], ["a", -2, "*"]] + wanted_render = [ "2 a", "a * 3", "-2 a", "a * ( -2 )"] + for (i,e) in enumerate(exps): + rend = txt_render(e) + self.assertEqual(rend, wanted_render[i]) + + def test_mult_fraction(self): + exps = [ [2, Fraction(1,2), "*"], [Fraction(1,2), 3, "*"]] + wanted_render = [ "2 * 1 / 2", "1 / 2 * 3"] + for (i,e) in enumerate(exps): + rend = txt_render(e) + self.assertEqual(rend, wanted_render[i]) + + def test_mult_exp(self): + pass + + def test_slash(self): + pass + + +if __name__ == '__main__': + unittest.main() + + + + + + + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del +