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__/
*.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
# 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()

View File

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

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