Packaging files
This commit is contained in:
0
pymath/__init__.py
Normal file
0
pymath/__init__.py
Normal file
31
pymath/arithmetic.py
Normal file
31
pymath/arithmetic.py
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/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
|
418
pymath/expression.py
Normal file
418
pymath/expression.py
Normal file
@@ -0,0 +1,418 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
from generic import Stack, flatten_list, expand_list
|
||||
from fraction import Fraction
|
||||
from renders import txt_render, post2in_fix, tex_render
|
||||
from formal import FormalExp
|
||||
|
||||
class Expression(object):
|
||||
"""A calculus expression. Today it can andle only expression with numbers later it will be able to manipulate unknown"""
|
||||
|
||||
PRIORITY = {"^": 5, "*" : 3, "/": 4, ":": 3, "+": 2, "-":2, "(": 1}
|
||||
|
||||
def __init__(self, exp):
|
||||
""" Initiate the expression
|
||||
|
||||
:param exp: the expression. It can be a string or a list of tokens. It can be infix or postfix expression
|
||||
"""
|
||||
if type(exp) == str:
|
||||
self._exp = exp
|
||||
self.tokens = self.str2tokens(exp) # les tokens seront alors stockés dans self.tokens temporairement
|
||||
elif type(exp) == list:
|
||||
self.tokens = exp
|
||||
|
||||
self._infix_tokens = None
|
||||
self._postfix_tokens = None
|
||||
|
||||
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) to string
|
||||
|
||||
"""
|
||||
if not self.can_go_further():
|
||||
yield render(self.postfix_tokens)
|
||||
else:
|
||||
self.compute_exp()
|
||||
old_s = ''
|
||||
for s in self.steps:
|
||||
new_s = render(s)
|
||||
# Astuce pour éviter d'avoir deux fois la même étape (par exemple pour la transfo d'une division en fraction)
|
||||
if new_s != old_s:
|
||||
old_s = new_s
|
||||
yield new_s
|
||||
for s in self.child.simplify(render = render):
|
||||
if old_s != s:
|
||||
yield s
|
||||
|
||||
def can_go_further(self):
|
||||
"""Check whether it's a last step or not. If not create self.child the next expression.
|
||||
:returns: 1 if it's not the last step, 0 otherwise
|
||||
"""
|
||||
if len(self.tokens) == 1:
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
def compute_exp(self):
|
||||
""" Create self.child with self.steps to go up to it """
|
||||
self.steps = [self.postfix_tokens]
|
||||
|
||||
tokenList = self.postfix_tokens.copy()
|
||||
tmpTokenList = []
|
||||
|
||||
while len(tokenList) > 2:
|
||||
# on va chercher les motifs du genre A B + pour les calculer
|
||||
if self.isNumber(tokenList[0]) and self.isNumber(tokenList[1]) and self.isOperator(tokenList[2]):
|
||||
|
||||
# S'il y a une opération à faire
|
||||
op1 = tokenList[0]
|
||||
op2 = tokenList[1]
|
||||
token = tokenList[2]
|
||||
|
||||
res = self.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)
|
||||
|
||||
if len(steps[:-1]) > 0:
|
||||
self.steps += [flatten_list(s) for s in steps[:-1]]
|
||||
|
||||
self.child = Expression(steps[-1])
|
||||
|
||||
## ---------------------
|
||||
## String parsing
|
||||
|
||||
## @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 = ['']
|
||||
|
||||
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)
|
||||
|
||||
|
||||
# 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.isalpha():
|
||||
# If "3x", ")x" or "yx"
|
||||
if self.isNumber(tokens[-1]) \
|
||||
or tokens[-1] == ")" \
|
||||
or type(tokens[-1]) == FormalExp:
|
||||
tokens.append("*")
|
||||
tokens.append(FormalExp(letter = character))
|
||||
|
||||
# Special case for "-" at the begining of an expression or before "("
|
||||
elif tokens[-1] == "-" \
|
||||
or str(tokens[-2]) in " (":
|
||||
tokens[-1] = - FormalExp(letter = character)
|
||||
|
||||
else:
|
||||
tokens.append(FormalExp(letter = character))
|
||||
|
||||
elif character in "+-*/):^":
|
||||
tokens.append(character)
|
||||
|
||||
elif character in "(":
|
||||
# If "3(", ")(" or "x("
|
||||
if self.isNumber(tokens[-1]) \
|
||||
or tokens[-1] == ")" \
|
||||
or type(tokens[-1]) == FormalExp:
|
||||
tokens.append("*")
|
||||
tokens.append(character)
|
||||
|
||||
elif character != " ":
|
||||
raise ValueError("{} is an unvalid character".format(character))
|
||||
|
||||
return tokens[1:]
|
||||
|
||||
# ---------------------
|
||||
# "fix" recognition
|
||||
|
||||
@classmethod
|
||||
def get_fix(self, tokens):
|
||||
""" Give the "fix" of an expression
|
||||
[A, +, B] -> infix, or if there is parenthesis it is infix
|
||||
[+, A, B] -> prefix
|
||||
[A, B, +] -> postfix
|
||||
/!\ does not verify if the expression is correct/computable!
|
||||
|
||||
:param exp: the expression (list of token)
|
||||
:returns: the "fix" (infix, postfix, prefix)
|
||||
|
||||
"""
|
||||
if self.isOperator(tokens[0]):
|
||||
return "prefix"
|
||||
elif "(" in tokens:
|
||||
return "infix"
|
||||
elif not self.isOperator(tokens[0]) and not self.isOperator(tokens[1]):
|
||||
return "postfix"
|
||||
else:
|
||||
return "infix"
|
||||
|
||||
def feed_fix(self):
|
||||
""" Recognize the fix of self.tokens and stock tokens in self.[fix]_tokens """
|
||||
if len(self.tokens) > 1:
|
||||
fix = self.get_fix(self.tokens)
|
||||
else:
|
||||
fix = "postfix" # Completement arbitraire mais on s'en fiche!
|
||||
|
||||
setattr(self, fix+"_tokens", self.tokens)
|
||||
|
||||
|
||||
# ----------------------
|
||||
# Expressions - tokens manipulation
|
||||
|
||||
@property
|
||||
def infix_tokens(self):
|
||||
""" Return infix list of tokens. Verify if it has already been computed and compute it if not
|
||||
|
||||
:returns: infix list of tokens
|
||||
"""
|
||||
if self._infix_tokens:
|
||||
return self._infix_tokens
|
||||
|
||||
elif self._postfix_tokens:
|
||||
self._infix_tokens = post2in_fix(self._postfix_tokens)
|
||||
return self._infix_tokens
|
||||
|
||||
else:
|
||||
raise ValueError("Unkown fix")
|
||||
|
||||
@infix_tokens.setter
|
||||
def infix_tokens(self, val):
|
||||
self._infix_tokens = val
|
||||
|
||||
@property
|
||||
def postfix_tokens(self):
|
||||
""" Return postfix list of tokens. Verify if it has already been computed and compute it if not
|
||||
|
||||
:returns: postfix list of tokens
|
||||
"""
|
||||
if self._postfix_tokens:
|
||||
return self._postfix_tokens
|
||||
|
||||
elif self._infix_tokens:
|
||||
self._postfix_tokens = self.in2post_fix(self._infix_tokens)
|
||||
return self._postfix_tokens
|
||||
|
||||
else:
|
||||
raise ValueError("Unkown fix")
|
||||
|
||||
@postfix_tokens.setter
|
||||
def postfix_tokens(self, val):
|
||||
self._postfix_tokens = val
|
||||
|
||||
# ----------------------
|
||||
# "fix" tranformations
|
||||
|
||||
@classmethod
|
||||
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.
|
||||
@return: the corresponding postfix list of tokens.
|
||||
|
||||
>>> Expression.in2post_fix(['(', 2, '+', 5, '-', 1, ')', '/', '(', 3, '*', 4, ')'])
|
||||
[2, 5, '+', 1, '-', 3, 4, '*', '/']
|
||||
"""
|
||||
opStack = Stack()
|
||||
postfixList = []
|
||||
|
||||
for token in infix_tokens:
|
||||
if token == "(":
|
||||
opStack.push(token)
|
||||
elif token == ")":
|
||||
topToken = opStack.pop()
|
||||
while topToken != "(":
|
||||
postfixList.append(topToken)
|
||||
topToken = opStack.pop()
|
||||
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 (cls.PRIORITY[opStack.peek()] >= cls.PRIORITY[token]):
|
||||
postfixList.append(opStack.pop())
|
||||
opStack.push(token)
|
||||
else:
|
||||
postfixList.append(token)
|
||||
|
||||
while not opStack.isEmpty():
|
||||
postfixList.append(opStack.pop())
|
||||
|
||||
return postfixList
|
||||
|
||||
## ---------------------
|
||||
## Computing the expression
|
||||
|
||||
@staticmethod
|
||||
def doMath(op, op1, op2):
|
||||
"""Compute "op1 op op2" or create a fraction
|
||||
|
||||
:param op: operator
|
||||
:param op1: first operande
|
||||
:param op2: second operande
|
||||
:returns: string representing the result
|
||||
|
||||
"""
|
||||
if op == "/":
|
||||
ans = [Fraction(op1, op2)]
|
||||
ans += ans[0].simplify()
|
||||
return ans
|
||||
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)
|
||||
|
||||
|
||||
## ---------------------
|
||||
## Recognize numbers and operators
|
||||
|
||||
@staticmethod
|
||||
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 or \
|
||||
type(exp) == FormalExp
|
||||
|
||||
@staticmethod
|
||||
def isOperator(exp):
|
||||
"""Check if the expression is an opération in "+-*/:^"
|
||||
|
||||
:param exp: an expression
|
||||
:returns: boolean
|
||||
|
||||
"""
|
||||
return (type(exp) == str and exp in "+-*/:^")
|
||||
|
||||
|
||||
def test(exp):
|
||||
a = Expression(exp)
|
||||
#for i in a.simplify():
|
||||
#for i in a.simplify(render = txt_render):
|
||||
for i in a.simplify(render = tex_render):
|
||||
print(i)
|
||||
|
||||
print("\n")
|
||||
|
||||
if __name__ == '__main__':
|
||||
#exp = "2 ^ 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 )^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)
|
||||
|
||||
#exp="-2*4(12 + 1)(3-12)"
|
||||
#test(exp)
|
||||
|
||||
#exp="-2+a+(12 + 1)(3-12) : 34a"
|
||||
##test(exp)
|
||||
#e = Expression(exp)
|
||||
#print(e.render(render = tex_render))
|
||||
|
||||
#exp="-2*b+a(12 + 1)(3-12)"
|
||||
#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()
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
219
pymath/fraction.py
Normal file
219
pymath/fraction.py
Normal file
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
from .arithmetic import gcd
|
||||
|
||||
class Fraction(object):
|
||||
"""Fractions!"""
|
||||
|
||||
def __init__(self, num, denom = 1):
|
||||
"""To initiate a fraction we need a numerator and a denominator
|
||||
|
||||
:param num: the numerator
|
||||
:param denom: the denominator
|
||||
|
||||
"""
|
||||
self._num = num
|
||||
self._denom = denom
|
||||
|
||||
def simplify(self):
|
||||
"""Simplify the fraction
|
||||
|
||||
:returns: steps to simplify the fraction or the fraction if there is nothing to do
|
||||
"""
|
||||
steps = []
|
||||
|
||||
if self._denom < 0:
|
||||
n_frac = Fraction(-self._num, -self._denom)
|
||||
steps.append(n_frac)
|
||||
|
||||
gcd_ = gcd(abs(self._num), abs(self._denom))
|
||||
if self._num == self._denom:
|
||||
n_frac = Fraction(1,1)
|
||||
steps.append(n_frac)
|
||||
|
||||
elif gcd_ != 1:
|
||||
n_frac = Fraction(self._num // gcd_ , self._denom // gcd_)
|
||||
#steps.append("( {reste1} * {gcd} ) / ( {reste2} * {gcd} )".format(reste1 = n_frac._num, reste2 = n_frac._denom, gcd = gcd_))
|
||||
steps.append([n_frac._num, gcd_, '*', n_frac._denom, gcd_, '*', '/' ])
|
||||
|
||||
# Certainement le truc le plus moche que j'ai jamais fait... On ne met que des strings dans steps puis au dernier moment on met une fraction. C'est moche de ma part
|
||||
steps.append(n_frac)
|
||||
|
||||
return steps
|
||||
|
||||
def __str__(self):
|
||||
if self._denom == 1:
|
||||
return str(self._num)
|
||||
else:
|
||||
return str(self._num) + " / " + str(self._denom)
|
||||
|
||||
def __repr__(self):
|
||||
return "< Fraction " + self.__str__() + ">"
|
||||
|
||||
def __add__(self, other):
|
||||
if type(other) == Fraction:
|
||||
#cool
|
||||
number = other
|
||||
else:
|
||||
number = Fraction(other)
|
||||
|
||||
steps = []
|
||||
|
||||
if self._denom == number._denom:
|
||||
com_denom = self._denom
|
||||
num1 = self._num
|
||||
num2 = number._num
|
||||
|
||||
else:
|
||||
gcd_denom = gcd(self._denom, number._denom)
|
||||
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
|
||||
|
||||
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
|
||||
number = other
|
||||
else:
|
||||
number = Fraction(other)
|
||||
|
||||
steps = []
|
||||
|
||||
if self._denom == number._denom:
|
||||
com_denom = self._denom
|
||||
num1 = self._num
|
||||
num2 = number._num
|
||||
|
||||
else:
|
||||
gcd_denom = gcd(self._denom, number._denom)
|
||||
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
|
||||
|
||||
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:
|
||||
#cool
|
||||
number = other
|
||||
else:
|
||||
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, '*', '/'])
|
||||
|
||||
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
|
||||
number = other
|
||||
else:
|
||||
number = Fraction(other)
|
||||
|
||||
steps = []
|
||||
number = Fraction(number._denom, number._num)
|
||||
steps += self * number
|
||||
|
||||
return steps
|
||||
|
||||
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:
|
||||
return (self._num / self._denom) <= other
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
f = Fraction(1, 12)
|
||||
g = Fraction(1, 12)
|
||||
h = Fraction(-1,5)
|
||||
t = Fraction(-4,5)
|
||||
print("---------")
|
||||
for i in (f - 1):
|
||||
print(i)
|
||||
print("---------")
|
||||
for i in (f + 1):
|
||||
print(i)
|
||||
print("---------")
|
||||
for i in (f + g):
|
||||
print(i)
|
||||
#print("---------")
|
||||
#for i in (f - g):
|
||||
# print(i)
|
||||
#print("---------")
|
||||
#for i in (f * g):
|
||||
# print(i)
|
||||
#print("---------")
|
||||
#for i in (h + t):
|
||||
# print(i)
|
||||
#print("---------")
|
||||
#for i in (h - t):
|
||||
# print(i)
|
||||
#print("---------")
|
||||
#for i in (h * t):
|
||||
# print(i)
|
||||
#print("---------")
|
||||
#for i in (h / t):
|
||||
# print(i)
|
||||
|
||||
#print(f.simplify())
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
195
pymath/generic.py
Normal file
195
pymath/generic.py
Normal file
@@ -0,0 +1,195 @@
|
||||
#!/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
|
||||
|
||||
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(k1,k2)
|
||||
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
|
138
pymath/random_expression.py
Normal file
138
pymath/random_expression.py
Normal file
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
from random import randint
|
||||
from expression import Expression
|
||||
from renders import tex_render, txt_render
|
||||
import re
|
||||
|
||||
class RdExpression(object):
|
||||
"""A generator of random expression builder"""
|
||||
|
||||
def __init__(self, form, conditions = []):
|
||||
"""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 {})
|
||||
|
||||
"""
|
||||
self._form = form
|
||||
self._conditions = conditions
|
||||
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 = re.findall(pattern, self._form)
|
||||
varia = set(varia)
|
||||
self._2replaced = varia
|
||||
|
||||
return varia
|
||||
|
||||
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
|
||||
|
||||
"""
|
||||
return render(self.raw_exp(val_min, val_max).postfix_tokens)
|
||||
|
||||
def raw_exp(self, val_min = -10, val_max = 10):
|
||||
"""Same as __call_ but returns an Expression object
|
||||
|
||||
: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 Expression(exp)
|
||||
|
||||
def gene_varia(self, val_min = -10, val_max = 10):
|
||||
"""RAndomly generates variables/letters
|
||||
|
||||
"""
|
||||
for l in self._letters:
|
||||
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_varia))
|
||||
else:
|
||||
return True
|
||||
|
||||
def desc_rdExp(rdExp):
|
||||
from renders import tex_render
|
||||
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 [0,1]", "{b} not in [0,1]"]
|
||||
rdExp1 = RdExpression(form, cond)
|
||||
desc_rdExp(rdExp1)
|
||||
rdExp2 = RdExpression(form)
|
||||
desc_rdExp(rdExp2)
|
||||
#form = "{a+a/10}x + {a} + 2*{b}"
|
||||
#cond = ["{a} + {b} in [1, 2, 3, 4, 5]", "{a} not in [0,1]", "{b} not in [0,1]"]
|
||||
#rdExp3 = RdExpression(form)
|
||||
#desc_rdExp(rdExp3)
|
||||
|
||||
form1 = "{a**2}x^2 + {2*a*b}x + {b**2}"
|
||||
cond1 = ["{a} != 0", "{b} != 0"]
|
||||
rdExp1 = RdExpression(form1, cond1)
|
||||
desc_rdExp(rdExp1)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
208
pymath/render.py
Normal file
208
pymath/render.py
Normal file
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
from generic import Stack,flatten_list
|
||||
from fraction import Fraction
|
||||
from formal import FormalExp
|
||||
|
||||
class Render(object):
|
||||
"""A class which aims to create render functions from three dictionnaries:
|
||||
- op_infix: dict of caracters
|
||||
- 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 = {int: str, Fraction: str, FormalExp: 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:
|
||||
res = flist([op1 , self.op_infix[token] , 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:
|
||||
return 1
|
||||
|
||||
# Si c'est un expression formelle
|
||||
elif type(operande) == FormalExp:
|
||||
if operator in ["*", "/", "^"]:
|
||||
if len(operande) > 1 \
|
||||
or operande.master_coef() < 0:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
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 - 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 \
|
||||
or type(exp) == FormalExp
|
||||
|
||||
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
|
Reference in New Issue
Block a user