Merge branch 'dev'
Conflicts: pymath/expression.py
This commit is contained in:
commit
a61e3e86ee
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
dist/
|
||||
|
9
MANIFEST
Normal file
9
MANIFEST
Normal 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
9
TODO
Normal 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
|
@ -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
|
409
calculus.py
409
calculus.py
@ -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
|
105
generic.py
105
generic.py
@ -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
0
pymath/__init__.py
Normal file
44
pymath/arithmetic.py
Normal file
44
pymath/arithmetic.py
Normal 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
|
@ -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()
|
@ -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
255
pymath/generic.py
Normal 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
168
pymath/random_expression.py
Normal 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
209
pymath/render.py
Normal 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
103
pymath/renders.py
Normal 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
11
setup.py
Normal 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
0
test/__init__.py
Normal file
42
test/test_arithmetic.py
Normal file
42
test/test_arithmetic.py
Normal 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
94
test/test_expression.py
Normal 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
119
test/test_fraction.py
Normal 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
76
test/test_generic.py
Normal 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
|
100
test/test_random_expression.py
Normal file
100
test/test_random_expression.py
Normal 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
108
test/test_renders.py
Normal 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
|
||||
|
Loading…
Reference in New Issue
Block a user