2013-08-09 09:35:14 +00:00
#!/usr/bin/env python
# encoding: utf-8
2013-11-01 21:58:42 +00:00
from generic import Stack , flatten_list , expand_list
from fraction import Fraction
def trace ( f ) :
""" Permet de visualiser des appels récursifs à la fonction décorée """
f . indent = 0
def g ( x ) :
print ( ' | ' * f . indent + ' |-- ' , f . __name__ , x )
f . indent + = 1
value = f ( x )
print ( ' | ' * f . indent + ' |-- ' , ' return ' , repr ( value ) )
f . indent - = 1
return value
return g
2013-08-09 09:35:14 +00:00
class Expression ( object ) :
""" A calculus expression. Today it can andle only expression with numbers later it will be able to manipulate unknown """
2013-11-01 21:58:42 +00:00
PRIORITY = { " * " : 3 , " / " : 3 , " + " : 2 , " - " : 2 , " ( " : 1 }
2013-08-09 11:08:24 +00:00
2013-08-09 09:35:14 +00:00
def __init__ ( self , exp ) :
2013-11-01 11:42:42 +00:00
""" Initiate the expression
2013-08-09 09:35:14 +00:00
: param exp : the expression . It can be a string or a list of tokens . It can be infix or postfix expression
"""
2013-11-01 21:58:42 +00:00
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
2013-08-09 09:35:14 +00:00
2013-11-01 21:58:42 +00:00
self . find_fix ( ) # Determine le fix et range la liste dans self.[fix]_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 )
"""
if not self . can_go_further ( ) :
yield render ( self . postfix_tokens ) # self.postfix_tokens devra être une propriété pour vérifier que quand on y accède, on a bien la forme voulu et si non, la calculer.
2013-08-09 10:03:32 +00:00
else :
2013-11-01 21:58:42 +00:00
self . compute_exp ( ) # crée self.children et éventuellement les étapes intermédiaires pour y arriver. /!\ les étapes sont sous la forme de listes de tokens en postfix
for s in self . steps :
yield render ( s )
for s in self . children . simplify ( render = render ) :
yield render ( s )
def can_go_further ( self ) :
""" Check whether it ' s a last step or not. If not create self.children 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.children 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 isNumber ( tokenList [ 0 ] ) and 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 = 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 )
self . steps + = [ steps [ : - 1 ] ]
self . children = Expression ( steps [ - 1 ] )
2013-11-01 11:42:42 +00:00
## ---------------------
## String parsing
## @classmethod ????
2013-11-01 21:58:42 +00:00
def str2tokens ( self , exp ) :
""" Parse the expression, ie tranform a string into a list of tokens
2013-11-01 11:42:42 +00:00
2013-11-01 21:58:42 +00:00
: param exp : The expression ( a string )
: returns : list of token
2013-11-01 11:42:42 +00:00
2013-11-01 21:58:42 +00:00
"""
return exp . split ( " " )
2013-11-01 11:42:42 +00:00
2013-11-01 21:58:42 +00:00
# ---------------------
# "fix" recognition
2013-11-01 11:42:42 +00:00
2013-11-01 21:58:42 +00:00
@classmethod
def get_fix ( self , tokens ) :
""" Give the " fix " of an expression
[ A , + , B ] - > 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 not self . isOperator ( tokens [ 0 ] ) and not self . isOperator ( tokens [ 1 ] ) :
return " postfix "
else :
return " infix "
def find_fix ( self ) :
""" Recognize the fix of self.tokens and stock tokens in self.[fix]_tokens """
fix = self . get_fix ( self . tokens )
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 hasattr ( self , " _infix_tokens " ) :
return self . _infix_tokens
elif hasattr ( self , " _postfix_tokens " ) :
self . infix_tokens = self . post2in_fix ( )
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 hasattr ( self , " _postfix_tokens " ) :
return self . _postfix_tokens
elif hasattr ( self , " _infix_tokens " ) :
self . postfix_tokens = self . in2post_fix ( )
return self . _postfix_tokens
else :
raise ValueError ( " Unkown fix " )
@postfix_tokens.setter
def postfix_tokens ( self , val ) :
self . _postfix_tokens = val
# ----------------------
# "fix" tranformations
def in2post_fix ( self ) :
""" From the self.infix_tokens list compute the corresponding self.postfix_tokens list """
opStack = Stack ( )
postfixList = [ ]
for token in self . infix_tokens :
if token == " ( " :
opStack . push ( token )
elif token == " ) " :
topToken = opStack . pop ( )
while topToken != " ( " :
postfixList . append ( topToken )
topToken = opStack . pop ( )
elif self . isOperator ( token ) :
# On doit ajouter la condition == str sinon python ne veut pas tester l'appartenance à la chaine de caractère.
while ( not opStack . isEmpty ( ) ) and ( self . PRIORITY [ opStack . peek ( ) ] > = self . PRIORITY [ token ] ) :
postfixList . append ( opStack . pop ( ) )
opStack . push ( token )
else :
postfixList . append ( token )
while not opStack . isEmpty ( ) :
postfixList . append ( opStack . pop ( ) )
self . postfix_tokens = postfixList
def post2in_fix ( self ) :
""" From the self.postfix_tokens list compute the corresponding self.infix_tokens list """
operandeStack = Stack ( )
for token in postfixTokens :
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 )
self . infix_tokens = flatten_list ( operandeStack . pop ( ) )
# ---------------------
# 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 isNumber ( operande ) and operande < 0 :
return 1
elif not 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 ] )
2013-11-01 11:42:42 +00:00
## ---------------------
## Computing the expression
2013-11-01 21:58:42 +00:00
@classmethod
def doMath ( self , op , op1 , op2 ) :
""" Compute " op1 op op2 " or create a fraction
2013-11-01 11:42:42 +00:00
2013-11-01 21:58:42 +00:00
: param op : operator
: param op1 : first operande
: param op2 : second operande
: returns : string representing the result
2013-11-01 11:42:42 +00:00
2013-11-01 21:58:42 +00:00
"""
operations = { " + " : " __add__ " , " - " : " __sub__ " , " * " : " __mul__ " }
if op == " / " :
ans = [ Fraction ( op1 , op2 ) ]
ans + = ans [ 0 ] . simplify ( )
return ans
else :
return getattr ( op1 , operations [ op ] ) ( op2 )
## ---------------------
## Recognize numbers and operators
2013-11-01 11:42:42 +00:00
2013-11-01 21:58:42 +00:00
@classmethod
def isNumber ( self , 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
@classmethod
def isOperator ( self , exp ) :
""" Check if the expression is an opération in " +-*/ "
: param exp : an expression
: returns : boolean
"""
return ( type ( exp ) == str and exp in " +-*/ " )
2013-11-01 11:42:42 +00:00
if __name__ == ' __main__ ' :
2013-11-01 21:58:42 +00:00
a = Expression ( " 10 + 1 * 3 " )
2013-11-01 11:42:42 +00:00
for i in a . simplify ( ) :
print ( i )
2013-08-09 09:35:14 +00:00
# -----------------------------
# Reglages pour 'vim'
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
# cursor: 16 del