Merge branch 'dev'

Conflicts:
	pymath/expression.py
This commit is contained in:
Lafrite 2014-02-28 14:18:51 +01:00
commit a61e3e86ee
22 changed files with 1590 additions and 690 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
__pycache__/ __pycache__/
*.pyc *.pyc
dist/

9
MANIFEST Normal file
View File

@ -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

9
TODO Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

0
pymath/__init__.py Normal file
View File

44
pymath/arithmetic.py Normal file
View File

@ -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

View File

@ -1,13 +1,16 @@
#!/usr/bin/env python #!/usr/bin/env python
# encoding: utf-8 # encoding: utf-8
from generic import Stack, flatten_list, expand_list from .generic import Stack, flatten_list, expand_list
from fraction import Fraction from .fraction import Fraction
from .renders import txt_render, post2in_fix, tex_render
__all__ = ['Expression']
class Expression(object): class Expression(object):
"""A calculus expression. Today it can andle only expression with numbers later it will be able to manipulate unknown""" """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): def __init__(self, exp):
""" Initiate the expression """ Initiate the expression
@ -25,13 +28,25 @@ class Expression(object):
self.feed_fix() # Determine le fix et range la liste dans self.[fix]_tokens 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 ## Mechanism functions
def simplify(self, render = lambda x:str(x)): def simplify(self, render = lambda x:str(x)):
""" Generator which return steps for computing the expression """ 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(): if not self.can_go_further():
@ -96,23 +111,52 @@ class Expression(object):
## --------------------- ## ---------------------
## String parsing ## String parsing
## @classmethod ???? @classmethod
def str2tokens(self, exp): def str2tokens(self, exp):
""" Parse the expression, ie tranform a string into a list of tokens """ Parse the expression, ie tranform a string into a list of tokens
/!\ float are not availiable yet!
:param exp: The expression (a string) :param exp: The expression (a string)
:returns: list of token :returns: list of token
""" """
tokens = exp.split(" ") tokens = ['']
for (i,t) in enumerate(tokens): for character in exp:
try: if character.isdigit():
tokens[i] = int(t) # for "big" numbers (like 2345)
except ValueError: if type(tokens[-1]) == int:
pass 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 # "fix" recognition
@ -161,7 +205,7 @@ class Expression(object):
return self._infix_tokens return self._infix_tokens
elif self._postfix_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 return self._infix_tokens
else: else:
@ -195,7 +239,7 @@ class Expression(object):
# "fix" tranformations # "fix" tranformations
@classmethod @classmethod
def in2post_fix(self, infix_tokens): def in2post_fix(cls, infix_tokens):
""" From the infix_tokens list compute the corresponding postfix_tokens list """ From the infix_tokens list compute the corresponding postfix_tokens list
@param infix_tokens: the infix list of tokens to transform into postfix form. @param infix_tokens: the infix list of tokens to transform into postfix form.
@ -215,9 +259,9 @@ class Expression(object):
while topToken != "(": while topToken != "(":
postfixList.append(topToken) postfixList.append(topToken)
topToken = opStack.pop() 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. # 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()) postfixList.append(opStack.pop())
opStack.push(token) opStack.push(token)
else: else:
@ -228,98 +272,6 @@ class Expression(object):
return postfixList 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 ## Computing the expression
@ -333,13 +285,18 @@ class Expression(object):
:returns: string representing the result :returns: string representing the result
""" """
operations = {"+": "__add__", "-": "__sub__", "*": "__mul__"}
if op == "/": if op == "/":
ans = [Fraction(op1, op2)] ans = [Fraction(op1, op2)]
ans += ans[0].simplify() ans += ans[0].simplify()
return ans return ans
else: 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 ## Recognize numbers and operators
@ -352,43 +309,44 @@ class Expression(object):
:returns: True if the expression can be a number and false otherwise :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 @staticmethod
def isOperator(exp): 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 :param exp: an expression
:returns: boolean :returns: boolean
""" """
return (type(exp) == str and exp in "+-*/") return (type(exp) == str and exp in "+-*/:^")
def test(exp): def test(exp):
a = Expression(exp) a = Expression(exp)
#for i in a.simplify(): #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(i)
print("\n") print("\n")
def render(tokens):
post_tokens = Expression.post2in_fix(tokens)
return ' '.join([str(t) for t in post_tokens])
if __name__ == '__main__': if __name__ == '__main__':
exp = "1 + 3 * 5" #exp = "2 ^ 3 * 5"
test(exp) #test(exp)
#exp = "1 + 3 * 5"
#test(exp)
#exp = "2 * 3 * 3 * 5" #exp = "2 * 3 * 3 * 5"
#test(exp) #test(exp)
exp = "2 * 3 + 3 * 5" #exp = "2 * 3 + 3 * 5"
test(exp) #test(exp)
exp = "2 * ( 3 + 4 ) + 3 * 5" #exp = "2 * ( 3 + 4 ) + 3 * 5"
test(exp) #test(exp)
#exp = "2 * ( 3 + 4 ) + ( 3 - 4 ) * 5" #exp = "2 * ( 3 + 4 ) + ( 3 - 4 ) * 5"
#test(exp) #test(exp)
@ -402,7 +360,7 @@ if __name__ == '__main__':
#exp = "2 + 5 * ( 3 - 4 )" #exp = "2 + 5 * ( 3 - 4 )"
#test(exp) #test(exp)
#exp = "( 2 + 5 ) * ( 3 - 4 )" #exp = "( 2 + 5 ) * ( 3 - 4 )^4"
#test(exp) #test(exp)
#exp = "( 2 + 5 ) * ( 3 * 4 )" #exp = "( 2 + 5 ) * ( 3 * 4 )"
@ -414,14 +372,27 @@ if __name__ == '__main__':
#exp = "( 2 + 5 ) / ( 3 * 4 ) + 1 / 12" #exp = "( 2 + 5 ) / ( 3 * 4 ) + 1 / 12"
#test(exp) #test(exp)
#exp = "( 2 + 5 ) / ( 3 * 4 ) + 1 / 2" #exp = "( 2+ 5 )/( 3 * 4 ) + 1 / 2"
#test(exp) #test(exp)
#exp = "( 2 + 5 ) / ( 3 * 4 ) + 1 / 12 + 5 * 5" #exp="(-2+5)/(3*4)+1/12+5*5"
#test(exp) #test(exp)
exp = "3 / 7 - 2 / 7 * 4 / 3" #exp="-2*4(12 + 1)(3-12)"
test(exp) #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 import doctest
doctest.testmod() doctest.testmod()

View File

@ -1,7 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python
# encoding: utf-8 # encoding: utf-8
from arithmetic import gcd from .arithmetic import gcd
__all__ = ['Fraction']
class Fraction(object): class Fraction(object):
"""Fractions!""" """Fractions!"""
@ -23,6 +25,11 @@ class Fraction(object):
""" """
steps = [] steps = []
if self._num == 0:
steps.append(0)
return steps
if self._denom < 0: if self._denom < 0:
n_frac = Fraction(-self._num, -self._denom) n_frac = Fraction(-self._num, -self._denom)
steps.append(n_frac) steps.append(n_frac)
@ -50,6 +57,9 @@ class Fraction(object):
def __repr__(self): def __repr__(self):
return "< Fraction " + self.__str__() + ">" return "< Fraction " + self.__str__() + ">"
def __float__(self):
return self._num / self._denom
def __add__(self, other): def __add__(self, other):
if type(other) == Fraction: if type(other) == Fraction:
@ -70,16 +80,12 @@ class Fraction(object):
coef1 = number._denom // gcd_denom coef1 = number._denom // gcd_denom
coef2 = self._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, "*", "/",'+']) steps.append([self._num, coef1, "*", self._denom, coef1, "*", '/', number._num, coef2, "*", number._denom, coef2, "*", "/",'+'])
com_denom = self._denom * coef1 com_denom = self._denom * coef1
num1 = self._num * coef1 num1 = self._num * coef1
num2 = number._num * coef2 num2 = number._num * coef2
#steps.append("( {num1} + {num2} ) / {denom}".format(num1 = num1, num2 = num2, denom = com_denom))
steps.append([num1, num2, '+', com_denom, '/']) steps.append([num1, num2, '+', com_denom, '/'])
num = num1 + num2 num = num1 + num2
@ -90,6 +96,42 @@ class Fraction(object):
return steps 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): def __sub__(self, other):
if type(other) == Fraction: if type(other) == Fraction:
#cool #cool
@ -109,14 +151,12 @@ class Fraction(object):
coef1 = number._denom // gcd_denom coef1 = number._denom // gcd_denom
coef2 = self._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, "*", "/",'-']) steps.append([self._num, coef1, "*", self._denom, coef1, "*", '/', number._num, coef2, "*", number._denom, coef2, "*", "/",'-'])
com_denom = self._denom * coef1 com_denom = self._denom * coef1
num1 = self._num * coef1 num1 = self._num * coef1
num2 = number._num * coef2 num2 = number._num * coef2
#steps.append("( {num1} - {num2} ) / {denom}".format(num1 = num1, num2 = num2, denom = com_denom))
steps.append([num1, num2, '-', com_denom, '/']) steps.append([num1, num2, '-', com_denom, '/'])
num = num1 - num2 num = num1 - num2
@ -126,6 +166,44 @@ class Fraction(object):
steps += ans_frac.simplify() steps += ans_frac.simplify()
return steps 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): def __mul__(self, other):
if type(other) == Fraction: if type(other) == Fraction:
@ -135,7 +213,6 @@ class Fraction(object):
number = Fraction(other) number = Fraction(other)
steps = [] 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, '*', '/']) steps.append([self._num, number._num, '*', self._denom, number._denom, '*', '/'])
@ -148,6 +225,26 @@ class Fraction(object):
return steps 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): def __truediv__(self, other):
if type(other) == Fraction: if type(other) == Fraction:
#cool #cool
@ -157,17 +254,46 @@ class Fraction(object):
steps = [] steps = []
number = Fraction(number._denom, number._num) number = Fraction(number._denom, number._num)
steps.append([self, number, "/"])
steps += self * number steps += self * number
return steps 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): def __lt__(self, other):
""" < """
if type(other) == Fraction: if type(other) == Fraction:
return (self._num / self._denom) < (other._num / other._denom) return (self._num / self._denom) < (other._num / other._denom)
else: else:
return (self._num / self._denom) < other return (self._num / self._denom) < other
def __le__(self, other): def __le__(self, other):
""" <= """
if type(other) == Fraction: if type(other) == Fraction:
return (self._num / self._denom) <= (other._num / other._denom) return (self._num / self._denom) <= (other._num / other._denom)
else: else:
@ -178,17 +304,17 @@ class Fraction(object):
if __name__ == '__main__': if __name__ == '__main__':
f = Fraction(1, 12) f = Fraction(1, 12)
g = Fraction(1, 12) g = Fraction(1, 12)
h = Fraction(-1,5) h = Fraction(1,-5)
t = Fraction(-4,5) t = Fraction(4,5)
print("---------") print("---------")
for i in (f - 1): for i in (1 + h):
print(i) print(i)
print("---------") print("---------")
for i in (f + 1): #for i in (f + t):
print(i) # print(i)
print("---------") #print("---------")
for i in (f + g): #for i in (f + g):
print(i) # print(i)
#print("---------") #print("---------")
#for i in (f - g): #for i in (f - g):
# print(i) # print(i)

255
pymath/generic.py Normal file
View File

@ -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

168
pymath/random_expression.py Normal file
View File

@ -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

209
pymath/render.py Normal file
View File

@ -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

103
pymath/renders.py Normal file
View File

@ -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

11
setup.py Normal file
View File

@ -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'],
)

0
test/__init__.py Normal file
View File

42
test/test_arithmetic.py Normal file
View File

@ -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

94
test/test_expression.py Normal file
View File

@ -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

119
test/test_fraction.py Normal file
View File

@ -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

76
test/test_generic.py Normal file
View File

@ -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

View File

@ -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

108
test/test_renders.py Normal file
View File

@ -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