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__/
|
__pycache__/
|
||||||
*.pyc
|
*.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
|
#!/usr/bin/env python
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
|
||||||
from generic import Stack, flatten_list, expand_list
|
from .generic import Stack, flatten_list, expand_list
|
||||||
from fraction import Fraction
|
from .fraction import Fraction
|
||||||
|
from .renders import txt_render, post2in_fix, tex_render
|
||||||
|
|
||||||
|
__all__ = ['Expression']
|
||||||
|
|
||||||
class Expression(object):
|
class Expression(object):
|
||||||
"""A calculus expression. Today it can andle only expression with numbers later it will be able to manipulate unknown"""
|
"""A calculus expression. Today it can andle only expression with numbers later it will be able to manipulate unknown"""
|
||||||
|
|
||||||
PRIORITY = {"*" : 3, "/": 4, "+": 2, "-":2, "(": 1}
|
PRIORITY = {"^": 5, "*" : 3, "/": 4, ":": 3, "+": 2, "-":2, "(": 1}
|
||||||
|
|
||||||
def __init__(self, exp):
|
def __init__(self, exp):
|
||||||
""" Initiate the expression
|
""" Initiate the expression
|
||||||
@ -25,13 +28,25 @@ class Expression(object):
|
|||||||
|
|
||||||
self.feed_fix() # Determine le fix et range la liste dans self.[fix]_tokens
|
self.feed_fix() # Determine le fix et range la liste dans self.[fix]_tokens
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Overload str as it aim to be use in console the render is txt_render"""
|
||||||
|
return txt_render(self.postfix_tokens)
|
||||||
|
|
||||||
|
def render(self, render = lambda x:str(x)):
|
||||||
|
""" Same as __str__ but accept render as argument
|
||||||
|
:param render: function which render the list of token (postfix form) to string
|
||||||
|
|
||||||
|
"""
|
||||||
|
# TODO: I don't like the name of this method |ven. janv. 17 12:48:14 CET 2014
|
||||||
|
return render(self.postfix_tokens)
|
||||||
|
|
||||||
## ---------------------
|
## ---------------------
|
||||||
## Mechanism functions
|
## Mechanism functions
|
||||||
|
|
||||||
def simplify(self, render = lambda x:str(x)):
|
def simplify(self, render = lambda x:str(x)):
|
||||||
""" Generator which return steps for computing the expression
|
""" Generator which return steps for computing the expression
|
||||||
|
|
||||||
@param render: function which render the list of token (postfix form now)
|
:param render: function which render the list of token (postfix form now) to string
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not self.can_go_further():
|
if not self.can_go_further():
|
||||||
@ -96,23 +111,52 @@ class Expression(object):
|
|||||||
## ---------------------
|
## ---------------------
|
||||||
## String parsing
|
## String parsing
|
||||||
|
|
||||||
## @classmethod ????
|
@classmethod
|
||||||
def str2tokens(self, exp):
|
def str2tokens(self, exp):
|
||||||
""" Parse the expression, ie tranform a string into a list of tokens
|
""" Parse the expression, ie tranform a string into a list of tokens
|
||||||
|
|
||||||
|
/!\ float are not availiable yet!
|
||||||
|
|
||||||
:param exp: The expression (a string)
|
:param exp: The expression (a string)
|
||||||
:returns: list of token
|
:returns: list of token
|
||||||
|
|
||||||
"""
|
"""
|
||||||
tokens = exp.split(" ")
|
tokens = ['']
|
||||||
|
|
||||||
for (i,t) in enumerate(tokens):
|
for character in exp:
|
||||||
try:
|
if character.isdigit():
|
||||||
tokens[i] = int(t)
|
# for "big" numbers (like 2345)
|
||||||
except ValueError:
|
if type(tokens[-1]) == int:
|
||||||
pass
|
if tokens[-1] > 0:
|
||||||
|
tokens[-1] = tokens[-1]*10 + int(character)
|
||||||
|
else:
|
||||||
|
tokens[-1] = tokens[-1]*10 - int(character)
|
||||||
|
|
||||||
return tokens
|
|
||||||
|
# Special case for "-" at the begining of an expression or before "("
|
||||||
|
elif tokens[-1] == "-" and \
|
||||||
|
str(tokens[-2]) in " (+-*/:":
|
||||||
|
tokens[-1] = - int(character)
|
||||||
|
else:
|
||||||
|
tokens.append(int(character))
|
||||||
|
|
||||||
|
elif character in "+-*/):^":
|
||||||
|
tokens.append(character)
|
||||||
|
|
||||||
|
elif character in "(":
|
||||||
|
# If "3(", ")("
|
||||||
|
if self.isNumber(tokens[-1]) \
|
||||||
|
or tokens[-1] == ")" :
|
||||||
|
tokens.append("*")
|
||||||
|
tokens.append(character)
|
||||||
|
|
||||||
|
elif character == ".":
|
||||||
|
raise ValueError("No float number please")
|
||||||
|
|
||||||
|
elif character != " ":
|
||||||
|
raise ValueError("{} is an unvalid character".format(character))
|
||||||
|
|
||||||
|
return tokens[1:]
|
||||||
|
|
||||||
# ---------------------
|
# ---------------------
|
||||||
# "fix" recognition
|
# "fix" recognition
|
||||||
@ -161,7 +205,7 @@ class Expression(object):
|
|||||||
return self._infix_tokens
|
return self._infix_tokens
|
||||||
|
|
||||||
elif self._postfix_tokens:
|
elif self._postfix_tokens:
|
||||||
self._infix_tokens = self.post2in_fix(self._postfix_tokens)
|
self._infix_tokens = post2in_fix(self._postfix_tokens)
|
||||||
return self._infix_tokens
|
return self._infix_tokens
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@ -195,7 +239,7 @@ class Expression(object):
|
|||||||
# "fix" tranformations
|
# "fix" tranformations
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def in2post_fix(self, infix_tokens):
|
def in2post_fix(cls, infix_tokens):
|
||||||
""" From the infix_tokens list compute the corresponding postfix_tokens list
|
""" From the infix_tokens list compute the corresponding postfix_tokens list
|
||||||
|
|
||||||
@param infix_tokens: the infix list of tokens to transform into postfix form.
|
@param infix_tokens: the infix list of tokens to transform into postfix form.
|
||||||
@ -215,9 +259,9 @@ class Expression(object):
|
|||||||
while topToken != "(":
|
while topToken != "(":
|
||||||
postfixList.append(topToken)
|
postfixList.append(topToken)
|
||||||
topToken = opStack.pop()
|
topToken = opStack.pop()
|
||||||
elif self.isOperator(token):
|
elif cls.isOperator(token):
|
||||||
# On doit ajouter la condition == str sinon python ne veut pas tester l'appartenance à la chaine de caractère.
|
# On doit ajouter la condition == str sinon python ne veut pas tester l'appartenance à la chaine de caractère.
|
||||||
while (not opStack.isEmpty()) and (self.PRIORITY[opStack.peek()] >= self.PRIORITY[token]):
|
while (not opStack.isEmpty()) and (cls.PRIORITY[opStack.peek()] >= cls.PRIORITY[token]):
|
||||||
postfixList.append(opStack.pop())
|
postfixList.append(opStack.pop())
|
||||||
opStack.push(token)
|
opStack.push(token)
|
||||||
else:
|
else:
|
||||||
@ -228,98 +272,6 @@ class Expression(object):
|
|||||||
|
|
||||||
return postfixList
|
return postfixList
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def post2in_fix(self, postfix_tokens):
|
|
||||||
""" From the postfix_tokens list compute the corresponding infix_tokens list
|
|
||||||
|
|
||||||
@param postfix_tokens: the postfix list of tokens to transform into infix form.
|
|
||||||
@return: the corresponding infix list of tokens if postfix_tokens.
|
|
||||||
|
|
||||||
>>> Expression.post2in_fix([2, 5, '+', 1, '-', 3, 4, '*', '/'])
|
|
||||||
['( ', 2, '+', 5, '-', 1, ' )', '/', '( ', 3, '*', 4, ' )']
|
|
||||||
>>> Expression.post2in_fix([2])
|
|
||||||
[2]
|
|
||||||
"""
|
|
||||||
operandeStack = Stack()
|
|
||||||
|
|
||||||
for token in postfix_tokens:
|
|
||||||
if self.isOperator(token):
|
|
||||||
op2 = operandeStack.pop()
|
|
||||||
|
|
||||||
if self.needPar(op2, token, "after"):
|
|
||||||
op2 = ["( ", op2, " )"]
|
|
||||||
op1 = operandeStack.pop()
|
|
||||||
|
|
||||||
if self.needPar(op1, token, "before"):
|
|
||||||
op1 = ["( ", op1, " )"]
|
|
||||||
res = [op1, token, op2]
|
|
||||||
|
|
||||||
operandeStack.push(res)
|
|
||||||
|
|
||||||
else:
|
|
||||||
operandeStack.push(token)
|
|
||||||
|
|
||||||
# Manip pour gerer les cas similaires au deuxième exemple
|
|
||||||
infix_tokens = operandeStack.pop()
|
|
||||||
if type(infix_tokens) == list:
|
|
||||||
infix_tokens = flatten_list(infix_tokens)
|
|
||||||
elif self.isNumber(infix_tokens):
|
|
||||||
infix_tokens = [infix_tokens]
|
|
||||||
|
|
||||||
return infix_tokens
|
|
||||||
|
|
||||||
# ---------------------
|
|
||||||
# Tools for placing parenthesis in infix notation
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def needPar(self, operande, operator, posi = "after"):
|
|
||||||
"""Says whether or not the operande needs parenthesis
|
|
||||||
|
|
||||||
:param operande: the operande
|
|
||||||
:param operator: the operator
|
|
||||||
:param posi: "after"(default) if the operande will be after the operator, "before" othewise
|
|
||||||
:returns: bollean
|
|
||||||
"""
|
|
||||||
if self.isNumber(operande) and operande < 0:
|
|
||||||
return 1
|
|
||||||
elif not self.isNumber(operande):
|
|
||||||
# Si c'est une grande expression ou un chiffre négatif
|
|
||||||
stand_alone = self.get_main_op(operande)
|
|
||||||
# Si la priorité de l'operande est plus faible que celle de l'opérateur
|
|
||||||
minor_priority = self.PRIORITY[self.get_main_op(operande)] < self.PRIORITY[operator]
|
|
||||||
# Si l'opérateur est -/ pour after ou juste / pour before
|
|
||||||
special = (operator in "-/" and posi == "after") or (operator in "/" and posi == "before")
|
|
||||||
|
|
||||||
return stand_alone and (minor_priority or special)
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_main_op(self, tokens):
|
|
||||||
"""Getting the main operation of the list of tokens
|
|
||||||
|
|
||||||
:param exp: the list of tokens
|
|
||||||
:returns: the main operation (+, -, * or /) or 0 if the expression is only one element
|
|
||||||
|
|
||||||
"""
|
|
||||||
parStack = Stack()
|
|
||||||
|
|
||||||
if len(tokens) == 1:
|
|
||||||
# Si l'expression n'est qu'un élément
|
|
||||||
return 0
|
|
||||||
|
|
||||||
main_op = []
|
|
||||||
|
|
||||||
for token in tokens:
|
|
||||||
if token == "(":
|
|
||||||
parStack.push(token)
|
|
||||||
elif token == ")":
|
|
||||||
parStack.pop()
|
|
||||||
elif self.isOperator(token) and parStack.isEmpty():
|
|
||||||
main_op.append(token)
|
|
||||||
|
|
||||||
return min(main_op, key = lambda s: self.PRIORITY[s])
|
|
||||||
|
|
||||||
## ---------------------
|
## ---------------------
|
||||||
## Computing the expression
|
## Computing the expression
|
||||||
|
|
||||||
@ -333,14 +285,19 @@ class Expression(object):
|
|||||||
:returns: string representing the result
|
:returns: string representing the result
|
||||||
|
|
||||||
"""
|
"""
|
||||||
operations = {"+": "__add__", "-": "__sub__", "*": "__mul__"}
|
|
||||||
if op == "/":
|
if op == "/":
|
||||||
ans = [Fraction(op1, op2)]
|
ans = [Fraction(op1, op2)]
|
||||||
ans += ans[0].simplify()
|
ans += ans[0].simplify()
|
||||||
return ans
|
return ans
|
||||||
else:
|
else:
|
||||||
|
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)
|
return getattr(op1,operations[op])(op2)
|
||||||
|
|
||||||
|
|
||||||
## ---------------------
|
## ---------------------
|
||||||
## Recognize numbers and operators
|
## Recognize numbers and operators
|
||||||
|
|
||||||
@ -352,43 +309,44 @@ class Expression(object):
|
|||||||
:returns: True if the expression can be a number and false otherwise
|
:returns: True if the expression can be a number and false otherwise
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return type(exp) == int or type(exp) == Fraction
|
return type(exp) == int or \
|
||||||
|
type(exp) == Fraction
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def isOperator(exp):
|
def isOperator(exp):
|
||||||
"""Check if the expression is an opération in "+-*/"
|
"""Check if the expression is an opération in "+-*/:^"
|
||||||
|
|
||||||
:param exp: an expression
|
:param exp: an expression
|
||||||
:returns: boolean
|
:returns: boolean
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return (type(exp) == str and exp in "+-*/")
|
return (type(exp) == str and exp in "+-*/:^")
|
||||||
|
|
||||||
|
|
||||||
def test(exp):
|
def test(exp):
|
||||||
a = Expression(exp)
|
a = Expression(exp)
|
||||||
#for i in a.simplify():
|
#for i in a.simplify():
|
||||||
for i in a.simplify(render = render):
|
#for i in a.simplify(render = txt_render):
|
||||||
|
for i in a.simplify(render = tex_render):
|
||||||
print(i)
|
print(i)
|
||||||
|
|
||||||
print("\n")
|
print("\n")
|
||||||
|
|
||||||
def render(tokens):
|
|
||||||
post_tokens = Expression.post2in_fix(tokens)
|
|
||||||
return ' '.join([str(t) for t in post_tokens])
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
exp = "1 + 3 * 5"
|
#exp = "2 ^ 3 * 5"
|
||||||
test(exp)
|
#test(exp)
|
||||||
|
|
||||||
|
#exp = "1 + 3 * 5"
|
||||||
|
#test(exp)
|
||||||
|
|
||||||
#exp = "2 * 3 * 3 * 5"
|
#exp = "2 * 3 * 3 * 5"
|
||||||
#test(exp)
|
#test(exp)
|
||||||
|
|
||||||
exp = "2 * 3 + 3 * 5"
|
#exp = "2 * 3 + 3 * 5"
|
||||||
test(exp)
|
#test(exp)
|
||||||
|
|
||||||
exp = "2 * ( 3 + 4 ) + 3 * 5"
|
#exp = "2 * ( 3 + 4 ) + 3 * 5"
|
||||||
test(exp)
|
#test(exp)
|
||||||
|
|
||||||
#exp = "2 * ( 3 + 4 ) + ( 3 - 4 ) * 5"
|
#exp = "2 * ( 3 + 4 ) + ( 3 - 4 ) * 5"
|
||||||
#test(exp)
|
#test(exp)
|
||||||
@ -402,7 +360,7 @@ if __name__ == '__main__':
|
|||||||
#exp = "2 + 5 * ( 3 - 4 )"
|
#exp = "2 + 5 * ( 3 - 4 )"
|
||||||
#test(exp)
|
#test(exp)
|
||||||
|
|
||||||
#exp = "( 2 + 5 ) * ( 3 - 4 )"
|
#exp = "( 2 + 5 ) * ( 3 - 4 )^4"
|
||||||
#test(exp)
|
#test(exp)
|
||||||
|
|
||||||
#exp = "( 2 + 5 ) * ( 3 * 4 )"
|
#exp = "( 2 + 5 ) * ( 3 * 4 )"
|
||||||
@ -414,14 +372,27 @@ if __name__ == '__main__':
|
|||||||
#exp = "( 2 + 5 ) / ( 3 * 4 ) + 1 / 12"
|
#exp = "( 2 + 5 ) / ( 3 * 4 ) + 1 / 12"
|
||||||
#test(exp)
|
#test(exp)
|
||||||
|
|
||||||
#exp = "( 2 + 5 ) / ( 3 * 4 ) + 1 / 2"
|
#exp = "( 2+ 5 )/( 3 * 4 ) + 1 / 2"
|
||||||
#test(exp)
|
#test(exp)
|
||||||
|
|
||||||
#exp = "( 2 + 5 ) / ( 3 * 4 ) + 1 / 12 + 5 * 5"
|
#exp="(-2+5)/(3*4)+1/12+5*5"
|
||||||
#test(exp)
|
#test(exp)
|
||||||
|
|
||||||
exp = "3 / 7 - 2 / 7 * 4 / 3"
|
#exp="-2*4(12 + 1)(3-12)"
|
||||||
test(exp)
|
#test(exp)
|
||||||
|
|
||||||
|
|
||||||
|
#exp="(-2+5)/(3*4)+1/12+5*5"
|
||||||
|
#test(exp)
|
||||||
|
|
||||||
|
# TODO: The next one doesn't work |ven. janv. 17 14:56:58 CET 2014
|
||||||
|
#exp="-2*(-a)(12 + 1)(3-12)"
|
||||||
|
#e = Expression(exp)
|
||||||
|
#print(e)
|
||||||
|
|
||||||
|
## Can't handle it yet!!
|
||||||
|
#exp="-(-2)"
|
||||||
|
#test(exp)
|
||||||
|
|
||||||
import doctest
|
import doctest
|
||||||
doctest.testmod()
|
doctest.testmod()
|
@ -1,7 +1,9 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
|
||||||
from arithmetic import gcd
|
from .arithmetic import gcd
|
||||||
|
|
||||||
|
__all__ = ['Fraction']
|
||||||
|
|
||||||
class Fraction(object):
|
class Fraction(object):
|
||||||
"""Fractions!"""
|
"""Fractions!"""
|
||||||
@ -23,6 +25,11 @@ class Fraction(object):
|
|||||||
"""
|
"""
|
||||||
steps = []
|
steps = []
|
||||||
|
|
||||||
|
if self._num == 0:
|
||||||
|
steps.append(0)
|
||||||
|
|
||||||
|
return steps
|
||||||
|
|
||||||
if self._denom < 0:
|
if self._denom < 0:
|
||||||
n_frac = Fraction(-self._num, -self._denom)
|
n_frac = Fraction(-self._num, -self._denom)
|
||||||
steps.append(n_frac)
|
steps.append(n_frac)
|
||||||
@ -51,6 +58,9 @@ class Fraction(object):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "< Fraction " + self.__str__() + ">"
|
return "< Fraction " + self.__str__() + ">"
|
||||||
|
|
||||||
|
def __float__(self):
|
||||||
|
return self._num / self._denom
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
if type(other) == Fraction:
|
if type(other) == Fraction:
|
||||||
#cool
|
#cool
|
||||||
@ -70,16 +80,12 @@ class Fraction(object):
|
|||||||
coef1 = number._denom // gcd_denom
|
coef1 = number._denom // gcd_denom
|
||||||
coef2 = self._denom // gcd_denom
|
coef2 = self._denom // gcd_denom
|
||||||
|
|
||||||
#steps.append("( {num1} * {coef1} ) / ( {den1} * {coef1} ) + ( {num2} * {coef2} ) / ( {den2} * {coef2} )".format(num1 = self._num, den1 = self._denom, coef1 = coef1, num2 = number._num, den2 = number._denom, coef2 = coef2))
|
|
||||||
|
|
||||||
steps.append([self._num, coef1, "*", self._denom, coef1, "*", '/', number._num, coef2, "*", number._denom, coef2, "*", "/",'+'])
|
steps.append([self._num, coef1, "*", self._denom, coef1, "*", '/', number._num, coef2, "*", number._denom, coef2, "*", "/",'+'])
|
||||||
|
|
||||||
com_denom = self._denom * coef1
|
com_denom = self._denom * coef1
|
||||||
num1 = self._num * coef1
|
num1 = self._num * coef1
|
||||||
num2 = number._num * coef2
|
num2 = number._num * coef2
|
||||||
|
|
||||||
#steps.append("( {num1} + {num2} ) / {denom}".format(num1 = num1, num2 = num2, denom = com_denom))
|
|
||||||
|
|
||||||
steps.append([num1, num2, '+', com_denom, '/'])
|
steps.append([num1, num2, '+', com_denom, '/'])
|
||||||
|
|
||||||
num = num1 + num2
|
num = num1 + num2
|
||||||
@ -90,6 +96,42 @@ class Fraction(object):
|
|||||||
|
|
||||||
return steps
|
return steps
|
||||||
|
|
||||||
|
def __radd__(self, other):
|
||||||
|
if type(other) == Fraction:
|
||||||
|
#cool
|
||||||
|
number = other
|
||||||
|
else:
|
||||||
|
number = Fraction(other)
|
||||||
|
|
||||||
|
steps = []
|
||||||
|
|
||||||
|
if number._denom == self._denom:
|
||||||
|
com_denom = number._denom
|
||||||
|
num1 = number._num
|
||||||
|
num2 = self._num
|
||||||
|
|
||||||
|
else:
|
||||||
|
gcd_denom = gcd(number._denom, self._denom)
|
||||||
|
coef1 = self._denom // gcd_denom
|
||||||
|
coef2 = number._denom // gcd_denom
|
||||||
|
|
||||||
|
steps.append([number._num, coef1, "*", number._denom, coef1, "*", '/', self._num, coef2, "*", self._denom, coef2, "*", "/",'+'])
|
||||||
|
|
||||||
|
com_denom = number._denom * coef1
|
||||||
|
num1 = number._num * coef1
|
||||||
|
num2 = self._num * coef2
|
||||||
|
|
||||||
|
steps.append([num1, num2, '+', com_denom, '/'])
|
||||||
|
|
||||||
|
num = num1 + num2
|
||||||
|
|
||||||
|
ans_frac = Fraction(num, com_denom)
|
||||||
|
steps.append(ans_frac)
|
||||||
|
steps += ans_frac.simplify()
|
||||||
|
|
||||||
|
return steps
|
||||||
|
|
||||||
|
|
||||||
def __sub__(self, other):
|
def __sub__(self, other):
|
||||||
if type(other) == Fraction:
|
if type(other) == Fraction:
|
||||||
#cool
|
#cool
|
||||||
@ -109,14 +151,12 @@ class Fraction(object):
|
|||||||
coef1 = number._denom // gcd_denom
|
coef1 = number._denom // gcd_denom
|
||||||
coef2 = self._denom // gcd_denom
|
coef2 = self._denom // gcd_denom
|
||||||
|
|
||||||
#steps.append("( {num1} * {coef1} ) / ( {den1} * {coef1} ) - ( {num2} * {coef2} ) / ( {den2} * {coef2} )".format(num1 = self._num, den1 = self._denom, coef1 = coef1, num2 = number._num, den2 = number._denom, coef2 = coef2))
|
|
||||||
steps.append([self._num, coef1, "*", self._denom, coef1, "*", '/', number._num, coef2, "*", number._denom, coef2, "*", "/",'-'])
|
steps.append([self._num, coef1, "*", self._denom, coef1, "*", '/', number._num, coef2, "*", number._denom, coef2, "*", "/",'-'])
|
||||||
|
|
||||||
com_denom = self._denom * coef1
|
com_denom = self._denom * coef1
|
||||||
num1 = self._num * coef1
|
num1 = self._num * coef1
|
||||||
num2 = number._num * coef2
|
num2 = number._num * coef2
|
||||||
|
|
||||||
#steps.append("( {num1} - {num2} ) / {denom}".format(num1 = num1, num2 = num2, denom = com_denom))
|
|
||||||
steps.append([num1, num2, '-', com_denom, '/'])
|
steps.append([num1, num2, '-', com_denom, '/'])
|
||||||
|
|
||||||
num = num1 - num2
|
num = num1 - num2
|
||||||
@ -127,6 +167,44 @@ class Fraction(object):
|
|||||||
|
|
||||||
return steps
|
return steps
|
||||||
|
|
||||||
|
def __rsub__(self, other):
|
||||||
|
if type(other) == Fraction:
|
||||||
|
#cool
|
||||||
|
number = other
|
||||||
|
else:
|
||||||
|
number = Fraction(other)
|
||||||
|
|
||||||
|
steps = []
|
||||||
|
|
||||||
|
if number._denom == self._denom:
|
||||||
|
com_denom = number._denom
|
||||||
|
num1 = number._num
|
||||||
|
num2 = self._num
|
||||||
|
|
||||||
|
else:
|
||||||
|
gcd_denom = gcd(number._denom, self._denom)
|
||||||
|
coef1 = self._denom // gcd_denom
|
||||||
|
coef2 = number._denom // gcd_denom
|
||||||
|
|
||||||
|
steps.append([number._num, coef1, "*", number._denom, coef1, "*", '/', self._num, coef2, "*", self._denom, coef2, "*", "/",'-'])
|
||||||
|
|
||||||
|
com_denom = number._denom * coef1
|
||||||
|
num1 = number._num * coef1
|
||||||
|
num2 = self._num * coef2
|
||||||
|
|
||||||
|
steps.append([num1, num2, '-', com_denom, '/'])
|
||||||
|
|
||||||
|
num = num1 - num2
|
||||||
|
|
||||||
|
ans_frac = Fraction(num, com_denom)
|
||||||
|
steps.append(ans_frac)
|
||||||
|
steps += ans_frac.simplify()
|
||||||
|
|
||||||
|
return steps
|
||||||
|
|
||||||
|
def __neg__(self):
|
||||||
|
return [Fraction(-self._num,self._denom)]
|
||||||
|
|
||||||
def __mul__(self, other):
|
def __mul__(self, other):
|
||||||
if type(other) == Fraction:
|
if type(other) == Fraction:
|
||||||
#cool
|
#cool
|
||||||
@ -135,7 +213,6 @@ class Fraction(object):
|
|||||||
number = Fraction(other)
|
number = Fraction(other)
|
||||||
|
|
||||||
steps = []
|
steps = []
|
||||||
#steps.append("( {num1} * {num2} ) / ( {denom1} * {denom2} )".format(num1 = self._num, num2 = number._num, denom1 = self._denom, denom2 = number._denom))
|
|
||||||
|
|
||||||
steps.append([self._num, number._num, '*', self._denom, number._denom, '*', '/'])
|
steps.append([self._num, number._num, '*', self._denom, number._denom, '*', '/'])
|
||||||
|
|
||||||
@ -148,6 +225,26 @@ class Fraction(object):
|
|||||||
|
|
||||||
return steps
|
return steps
|
||||||
|
|
||||||
|
def __rmul__(self, other):
|
||||||
|
if type(other) == Fraction:
|
||||||
|
#cool
|
||||||
|
number = other
|
||||||
|
else:
|
||||||
|
number = Fraction(other)
|
||||||
|
|
||||||
|
steps = []
|
||||||
|
|
||||||
|
steps.append([number._num, self._num, '*', number._denom, self._denom, '*', '/'])
|
||||||
|
|
||||||
|
num = self._num * number._num
|
||||||
|
denom = self._denom * number._denom
|
||||||
|
|
||||||
|
ans_frac = Fraction(num, denom)
|
||||||
|
steps.append(ans_frac)
|
||||||
|
steps += ans_frac.simplify()
|
||||||
|
|
||||||
|
return steps
|
||||||
|
|
||||||
def __truediv__(self, other):
|
def __truediv__(self, other):
|
||||||
if type(other) == Fraction:
|
if type(other) == Fraction:
|
||||||
#cool
|
#cool
|
||||||
@ -157,17 +254,46 @@ class Fraction(object):
|
|||||||
|
|
||||||
steps = []
|
steps = []
|
||||||
number = Fraction(number._denom, number._num)
|
number = Fraction(number._denom, number._num)
|
||||||
|
steps.append([self, number, "/"])
|
||||||
steps += self * number
|
steps += self * number
|
||||||
|
|
||||||
return steps
|
return steps
|
||||||
|
|
||||||
|
def __rtruediv__(self, other):
|
||||||
|
if type(other) == Fraction:
|
||||||
|
#cool
|
||||||
|
number = other
|
||||||
|
else:
|
||||||
|
number = Fraction(other)
|
||||||
|
|
||||||
|
steps = []
|
||||||
|
self_inv = Fraction(self._denom, self._num)
|
||||||
|
steps.append([number, self_inv, "/"])
|
||||||
|
steps += number * self_inv
|
||||||
|
|
||||||
|
return steps
|
||||||
|
|
||||||
|
def __abs__(self):
|
||||||
|
return Fraction(abs(self._num), abs(self._denom))
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
""" == """
|
||||||
|
if type(other) == Fraction:
|
||||||
|
number = other
|
||||||
|
else:
|
||||||
|
number = Fraction(other)
|
||||||
|
|
||||||
|
return self._num * number._denom == self._denom * number._num
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
|
""" < """
|
||||||
if type(other) == Fraction:
|
if type(other) == Fraction:
|
||||||
return (self._num / self._denom) < (other._num / other._denom)
|
return (self._num / self._denom) < (other._num / other._denom)
|
||||||
else:
|
else:
|
||||||
return (self._num / self._denom) < other
|
return (self._num / self._denom) < other
|
||||||
|
|
||||||
def __le__(self, other):
|
def __le__(self, other):
|
||||||
|
""" <= """
|
||||||
if type(other) == Fraction:
|
if type(other) == Fraction:
|
||||||
return (self._num / self._denom) <= (other._num / other._denom)
|
return (self._num / self._denom) <= (other._num / other._denom)
|
||||||
else:
|
else:
|
||||||
@ -178,17 +304,17 @@ class Fraction(object):
|
|||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
f = Fraction(1, 12)
|
f = Fraction(1, 12)
|
||||||
g = Fraction(1, 12)
|
g = Fraction(1, 12)
|
||||||
h = Fraction(-1,5)
|
h = Fraction(1,-5)
|
||||||
t = Fraction(-4,5)
|
t = Fraction(4,5)
|
||||||
print("---------")
|
print("---------")
|
||||||
for i in (f - 1):
|
for i in (1 + h):
|
||||||
print(i)
|
print(i)
|
||||||
print("---------")
|
print("---------")
|
||||||
for i in (f + 1):
|
#for i in (f + t):
|
||||||
print(i)
|
# print(i)
|
||||||
print("---------")
|
#print("---------")
|
||||||
for i in (f + g):
|
#for i in (f + g):
|
||||||
print(i)
|
# print(i)
|
||||||
#print("---------")
|
#print("---------")
|
||||||
#for i in (f - g):
|
#for i in (f - g):
|
||||||
# print(i)
|
# print(i)
|
255
pymath/generic.py
Normal file
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