Nice render (even with latex! :D) And seems to work

This commit is contained in:
lafrite 2013-12-08 22:24:27 +01:00
parent c25fee2135
commit 31b433faf5
2 changed files with 43 additions and 173 deletions

View File

@ -3,12 +3,12 @@
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 render import txt_render, post2in_fix, tex_render
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, "/": 3, "+": 2, "-":2, "(": 1} PRIORITY = {"*" : 3, "/": 3, "+": 2, "-":2, "(": 1}
TEXSYM = {"*" : " \\times ", "+" : " + " , "-" : " - "}
def __init__(self, exp): def __init__(self, exp):
""" Initiate the expression """ Initiate the expression
@ -162,7 +162,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:
@ -192,55 +192,6 @@ class Expression(object):
def postfix_tokens(self, val): def postfix_tokens(self, val):
self._postfix_tokens = val self._postfix_tokens = val
# ----------------------
# Latex render
@classmethod
def texRender(cls, postfix_tokens):
"""@todo: Docstring for texRender
:param postfix_tokens: the postfix list of tokens to transform into infix form.
:returns: the latex render ready to insert into a maht environment
>>> Expression.post2in_fix([2, 5, '+', 1, '-', 3, 4, '*', '/'])
['( ', 2, '+', 5, '-', 1, ' )', '/', '( ', 3, '*', 4, ' )']
"\frac{2 + 5 - 1}{3 \times 4)"
>>> Expression.post2in_fix([2])
"2"
"""
operandeStack = Stack()
for token in postfix_tokens:
if cls.isOperator(token):
op2 = operandeStack.pop()
op1 = operandeStack.pop()
if token == "/":
res = "\\frac{" + str(op1) + "}{" + str(op2) + "}"
else:
if cls.needPar(op2, token, "after"):
op2 = "\\left( " + str(op2) + " \\right) "
if cls.needPar(op1, token, "before"):
op2 = "\\left( " + str(op1) + " \\right) "
res = str(op1) + cls.TEXSYM[token] + str(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 cls.isNumber(infix_tokens):
infix_tokens = [infix_tokens]
return infix_tokens
# ---------------------- # ----------------------
# "fix" tranformations # "fix" tranformations
@ -278,103 +229,6 @@ class Expression(object):
return postfixList return postfixList
@classmethod
def post2in_fix(cls, 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 cls.isOperator(token):
op2 = operandeStack.pop()
if cls.needPar(op2, token, "after"):
op2 = ["( ", op2, " )"]
op1 = operandeStack.pop()
if cls.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 cls.isNumber(infix_tokens):
infix_tokens = [infix_tokens]
return infix_tokens
# ---------------------
# Tools for placing parenthesis in infix notation
@classmethod
def needPar(cls, 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 cls.isNumber(operande) and operande < 0:
return 1
elif not cls.isNumber(operande):
# Si c'est une grande expression ou un chiffre négatif
stand_alone = cls.get_main_op(operande)
# Si la priorité de l'operande est plus faible que celle de l'opérateur
minor_priority = cls.PRIORITY[cls.get_main_op(operande)] < cls.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(cls, 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
"""
print("tokens: ", tokens)
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 cls.isOperator(token) and parStack.isEmpty():
main_op.append(token)
print("main_op", main_op)
return min(main_op, key = lambda s: cls.PRIORITY[s])
## --------------------- ## ---------------------
## Computing the expression ## Computing the expression
@ -423,15 +277,12 @@ class Expression(object):
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 = Expression.texRender): #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 = "1 + 3 * 5"
test(exp) test(exp)

View File

@ -16,13 +16,14 @@ class Render(object):
PRIORITY = {"*" : 3, "/": 3, "+": 2, "-":2, "(": 1} PRIORITY = {"*" : 3, "/": 3, "+": 2, "-":2, "(": 1}
def __init__(self, op_infix = {}, op_postfix = {}, other = {}, join = " "): def __init__(self, op_infix = {}, op_postfix = {}, other = {}, join = " ", type_render = {int: str, Fraction: str}):
"""Initiate the render """Initiate the render
@param op_infix: the dictionnary of infix operator with how they have to be 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 op_postfix: the dictionnary of postfix operator with how they have to be render
@param other: other caracters like parenthesis. @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 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_infix = op_infix
@ -31,6 +32,7 @@ class Render(object):
# TODO: there may be issues with PRIORITY if a sign does not appear in PRIORITY # TODO: there may be issues with PRIORITY if a sign does not appear in PRIORITY
self.join = join self.join = join
self.type_render = type_render
self.operators = list(self.op_infix.keys()) + list(self.op_postfix.keys()) + list(self.other.keys()) self.operators = list(self.op_infix.keys()) + list(self.op_postfix.keys()) + list(self.other.keys())
@ -46,16 +48,15 @@ class Render(object):
for token in postfix_tokens: for token in postfix_tokens:
if self.isOperator(token): if self.isOperator(token):
op2 = operandeStack.pop() op2 = operandeStack.pop()
if self.needPar(op2, token, "after"): if self.needPar(op2, token, "after"):
op2 = [self.other["("] , op2 , self.other[")"]] op2 = [self.other["("] , op2 , self.other[")"]]
op1 = operandeStack.pop() op1 = operandeStack.pop()
if self.needPar(op1, token, "before"): if self.needPar(op1, token, "before"):
op1 = [self.other["("] , op1 , self.other[")"]] op1 = [self.other["("] , op1 , self.other[")"]]
if token in self.op_infix: if token in self.op_infix:
res = flist([op1 , self.op_infix[token] , op2]) res = flist([op1 , self.op_infix[token] , op2])
@ -70,7 +71,7 @@ class Render(object):
else: else:
operandeStack.push(token) operandeStack.push(token)
# Manip pour gerer les cas similaires au deuxième exemple # Manip pour gerer les cas de listes imbriquées dans d'autres listes
infix_tokens = operandeStack.pop() infix_tokens = operandeStack.pop()
if type(infix_tokens) == list or type(infix_tokens) == flist: if type(infix_tokens) == list or type(infix_tokens) == flist:
infix_tokens = flatten_list(infix_tokens) infix_tokens = flatten_list(infix_tokens)
@ -78,10 +79,23 @@ class Render(object):
infix_tokens = [infix_tokens] infix_tokens = [infix_tokens]
if self.join: if self.join:
return self.join.join([str(t) for t in infix_tokens]) return self.join.join(flatten_list([self.render_from_type(t) for t in infix_tokens]))
else: else:
return infix_tokens return infix_tokens
def render_from_type(self, op):
""" If the op is a number, it transforms it with type_render conditions
:param op: the operator
:returns: the op transformed if it's necessary
"""
if self.isNumber(op):
return self.type_render[type(op)](op)
else:
return op
# --------------------- # ---------------------
# Tools for placing parenthesis in infix notation # Tools for placing parenthesis in infix notation
@ -137,7 +151,8 @@ class Render(object):
## --------------------- ## ---------------------
## Recognize numbers and operators ## Recognize numbers and operators
def isNumber(self, exp): @staticmethod
def isNumber( exp):
"""Check if the expression can be a number which means that it is not a operator """Check if the expression can be a number which means that it is not a operator
:param exp: an expression :param exp: an expression
@ -167,32 +182,36 @@ txt_infix = {"+": "+", "-": "-", "*": "*", "/" : "/"}
txt_postfix = {} txt_postfix = {}
txt_other = {"(": "(", ")": ")"} txt_other = {"(": "(", ")": ")"}
txt = Render(txt_infix, txt_postfix, txt_other) txt_render = Render(txt_infix, txt_postfix, txt_other)
# ------------------------ # ------------------------
# A infix to postfix list convertor # A infix to postfix list convertor
i2p_infix = {"+": "+", "-": "-", "*": "*", "/" : "/"} p2i_infix = {"+": "+", "-": "-", "*": "*", "/" : "/"}
i2p_postfix = {} p2i_postfix = {}
i2p_other = {"(": "(", ")": ")"} p2i_other = {"(": "(", ")": ")"}
in2post_fix = Render(i2p_infix, i2p_postfix, i2p_other, join = False) post2in_fix = Render(p2i_infix, p2i_postfix, p2i_other, join = False)
# ------------------------ # ------------------------
# A latex render # A latex render
def texFrac(op1, op2): def texSlash(op1, op2):
if op1[0] == "(" and op1[-1] == ")": if not Render.isNumber(op1) and op1[0] == "(" and op1[-1] == ")":
op1 = op1[1:-1] op1 = op1[1:-1]
if op2[0] == "(" and op2[-1] == ")": if not Render.isNumber(op2) and op2[0] == "(" and op2[-1] == ")":
op2 = op2[1:-1] op2 = op2[1:-1]
return ["\\frac{" , op1 , "}{" , op2 , "}"] return ["\\frac{" , op1 , "}{" , op2 , "}"]
tex_infix = {"+": " + ", "-": " - ", "*": " * "} def texFrac(frac):
tex_postfix = {"/": texFrac} return ["\\frac{" , str(frac._num) , "}{" , str(frac._denom) , "}"]
tex_other = {"(": "(", ")": ")"}
tex = Render(tex_infix, tex_postfix, tex_other) tex_infix = {"+": " + ", "-": " - ", "*": " * "}
tex_postfix = {"/": texSlash}
tex_other = {"(": "(", ")": ")"}
tex_type_render = {int: str, Fraction: texFrac}
tex_render = Render(tex_infix, tex_postfix, tex_other, type_render = tex_type_render)
@ -202,7 +221,7 @@ if __name__ == '__main__':
exp = [2, 5, '+', 1, '-', 3, 4, '*', '/', 3, '+'] exp = [2, 5, '+', 1, '-', 3, 4, '*', '/', 3, '+']
print(tex(exp)) print(tex(exp))
exp = [2, 5, '+', 1, '-', 3, 4, '*', '/', 3, '+'] exp = [2, 5, '+', 1, '-', 3, 4, '*', '/', 3, '+']
print(in2post_fix(exp)) print(post2in_fix(exp))
# ----------------------------- # -----------------------------