Render done need to clean expression

This commit is contained in:
lafrite 2013-12-08 21:02:19 +01:00
parent a7fb8844b0
commit ead4e15d92
3 changed files with 256 additions and 72 deletions

View File

@ -8,6 +8,7 @@ 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
@ -191,11 +192,60 @@ 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
@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 +265,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:
@ -229,7 +279,7 @@ class Expression(object):
return postfixList return postfixList
@classmethod @classmethod
def post2in_fix(self, postfix_tokens): def post2in_fix(cls, postfix_tokens):
""" From the postfix_tokens list compute the corresponding infix_tokens list """ From the postfix_tokens list compute the corresponding infix_tokens list
@param postfix_tokens: the postfix list of tokens to transform into infix form. @param postfix_tokens: the postfix list of tokens to transform into infix form.
@ -243,14 +293,14 @@ class Expression(object):
operandeStack = Stack() operandeStack = Stack()
for token in postfix_tokens: for token in postfix_tokens:
if self.isOperator(token): if cls.isOperator(token):
op2 = operandeStack.pop() op2 = operandeStack.pop()
if self.needPar(op2, token, "after"): if cls.needPar(op2, token, "after"):
op2 = ["( ", op2, " )"] op2 = ["( ", op2, " )"]
op1 = operandeStack.pop() op1 = operandeStack.pop()
if self.needPar(op1, token, "before"): if cls.needPar(op1, token, "before"):
op1 = ["( ", op1, " )"] op1 = ["( ", op1, " )"]
res = [op1, token, op2] res = [op1, token, op2]
@ -263,7 +313,7 @@ class Expression(object):
infix_tokens = operandeStack.pop() infix_tokens = operandeStack.pop()
if type(infix_tokens) == list: if type(infix_tokens) == list:
infix_tokens = flatten_list(infix_tokens) infix_tokens = flatten_list(infix_tokens)
elif self.isNumber(infix_tokens): elif cls.isNumber(infix_tokens):
infix_tokens = [infix_tokens] infix_tokens = [infix_tokens]
return infix_tokens return infix_tokens
@ -272,7 +322,7 @@ class Expression(object):
# Tools for placing parenthesis in infix notation # Tools for placing parenthesis in infix notation
@classmethod @classmethod
def needPar(self, operande, operator, posi = "after"): def needPar(cls, operande, operator, posi = "after"):
"""Says whether or not the operande needs parenthesis """Says whether or not the operande needs parenthesis
:param operande: the operande :param operande: the operande
@ -280,13 +330,13 @@ class Expression(object):
:param posi: "after"(default) if the operande will be after the operator, "before" othewise :param posi: "after"(default) if the operande will be after the operator, "before" othewise
:returns: bollean :returns: bollean
""" """
if self.isNumber(operande) and operande < 0: if cls.isNumber(operande) and operande < 0:
return 1 return 1
elif not self.isNumber(operande): elif not cls.isNumber(operande):
# Si c'est une grande expression ou un chiffre négatif # Si c'est une grande expression ou un chiffre négatif
stand_alone = self.get_main_op(operande) stand_alone = cls.get_main_op(operande)
# Si la priorité de l'operande est plus faible que celle de l'opérateur # 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] minor_priority = cls.PRIORITY[cls.get_main_op(operande)] < cls.PRIORITY[operator]
# Si l'opérateur est -/ pour after ou juste / pour before # Si l'opérateur est -/ pour after ou juste / pour before
special = (operator in "-/" and posi == "after") or (operator in "/" and posi == "before") special = (operator in "-/" and posi == "after") or (operator in "/" and posi == "before")
@ -295,13 +345,16 @@ class Expression(object):
return 0 return 0
@classmethod @classmethod
def get_main_op(self, tokens): def get_main_op(cls, tokens):
"""Getting the main operation of the list of tokens """Getting the main operation of the list of tokens
:param exp: the list of tokens :param exp: the list of tokens
:returns: the main operation (+, -, * or /) or 0 if the expression is only one element :returns: the main operation (+, -, * or /) or 0 if the expression is only one element
""" """
print("tokens: ", tokens)
parStack = Stack() parStack = Stack()
if len(tokens) == 1: if len(tokens) == 1:
@ -315,10 +368,12 @@ class Expression(object):
parStack.push(token) parStack.push(token)
elif token == ")": elif token == ")":
parStack.pop() parStack.pop()
elif self.isOperator(token) and parStack.isEmpty(): elif cls.isOperator(token) and parStack.isEmpty():
main_op.append(token) main_op.append(token)
return min(main_op, key = lambda s: self.PRIORITY[s]) print("main_op", main_op)
return min(main_op, key = lambda s: cls.PRIORITY[s])
## --------------------- ## ---------------------
## Computing the expression ## Computing the expression
@ -368,7 +423,7 @@ 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 = render): for i in a.simplify(render = Expression.texRender):
print(i) print(i)
print("\n") print("\n")

184
render.py Normal file
View File

@ -0,0 +1,184 @@
#!/usr/bin/env python
# encoding: utf-8
from generic import Stack,flatten_list
from fraction import Fraction
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 = {"*" : 3, "/": 3, "+": 2, "-":2, "(": 1}
def __init__(self, op_infix = {}, op_postfix = {}, other = {}):
"""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.
"""
self.op_infix = op_infix
self.op_postfix = op_postfix
self.other = other
# TODO: there may be issues with PRIORITY
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["("] + str(op2) + self.other[")"]
op1 = operandeStack.pop()
if self.needPar(op1, token, "before"):
op1 = self.other["("] + str(op1) + self.other[")"]
if token in self.op_infix:
res = fstr(str(op1) + self.op_infix[token] + str(op2))
elif token in self.op_postfix:
res = fstr(self.op_postfix[token](str(op1), str(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 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
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
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
def isNumber(self, exp):
"""Check if the expression can be a number which means that it is not a 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 fstr(str):
"""Fake string - they are used to stock the main operation of an rendered expression"""
pass
txt_infix = {"+": " + ", "-": " - ", "*": " * ", "/" : " / "}
txt_postfix = {}
txt_other = {"(": "( ", ")": ") "}
txt_render = Render(txt_infix, txt_postfix, txt_other)
def texFrac(op1, op2):
if op1[0] == "(" and op1[-1] == ")":
op1 = op1[1:-1]
if op2[0] == "(" and op2[-1] == ")":
op2 = op2[1:-1]
return "\\frac{" + str(op1) + "}{" + str(op2) + "}"
tex_infix = {"+": " + ", "-": " - ", "*": " * "}
tex_postfix = {"/": texFrac}
tex_other = {"(": "( ", ")": " )"}
tex_render = Render(tex_infix, tex_postfix, tex_other)
if __name__ == '__main__':
#exp = [2, 5, '+', 1, '-', 3, 4, '*', '/']
#print(txt_render(exp))
exp = [2, 5, '+', 1, '-', 3, 4, '*', '/', 3, '+']
print(tex_render(exp))
# -----------------------------
# Reglages pour 'vim'
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
# cursor: 16 del

View File

@ -1,55 +0,0 @@
#!/usr/bin/env python
# encoding: utf-8
from generic import Stack
def post2in_fix(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. If nothing is set, it takes the value self.postfix_tokens
@return: the corresponding infix list of tokens if postfix_tokens is set. nothing otherwise but stock it in self.infix_tokens
>>> post2in_fix([2, 5, '+', 1, '-', 3, 4, '*', '/'])
['( ', 2, '+', 5, '-', 1, ' )', '/', '( ', 3, '*', 4, ' )']
"""
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)
infix_tokens = flatten_list(operandeStack.pop())
return infix_tokens
def textRender(postfix_tokens):
""" A text baser render
:param postfix_tokens: The postfix list of tokens
:returns: the text expression
"""
infix_tokens = post2in_fix(postfix_tokens)
return ' '.join(infix_tokens)
if __name__ == '__main__':
import doctest
doctest.testmod()
# -----------------------------
# Reglages pour 'vim'
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
# cursor: 16 del