explain works for Expression. Need to adapt Fraction and Polynom now

This commit is contained in:
Lafrite 2015-02-26 19:02:20 +01:00
parent d88c8ff738
commit 6d8b1786b3
3 changed files with 168 additions and 146 deletions

View File

@ -1,46 +0,0 @@
#!/usr/bin/env python
# encoding: utf-8
class Renderable_expression(object):
"""Docstring for Renderable_expression. """
def __init__(self):
"""@todo: to be defined1. """
class Computable_expression(Renderable_expression):
""" A computable_expression is an expression represented by a list of postfix tokens. It's a parent class of a more classical Expression, Fraction and Polynom (and later square root)
Child class will herits those methods
* simplify: Compute entirely the expression
* explain: Generator which return steps which leed to himself
"""
def __init__(self):
"""@todo: to be defined1. """
pass
def simplify(self):
""" Simplify the expression
:returns: the simplified expression with .steps attributes where steps are stocked
"""
pass
def explain(self):
""" Generate and render steps which leed to itself """
pass
# -----------------------------
# Reglages pour 'vim'
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
# cursor: 16 del

40
pymath/explicable.py Normal file
View File

@ -0,0 +1,40 @@
#!/usr/bin/env python
# encoding: utf-8
class Explicable(object):
""" An Explicable object is an object which can be explicable!
It's a parent class of a more classical Expression, Fraction and Polynom (and later square root)
Child class will have the following method
* explain: Generator which return steps which leed to himself
"""
def __init__(self, *args, **kwargs):
self.steps = []
def explain(self):
""" Generate and render steps which leed to itself """
old_s = ''
# les étapes pour l'atteindre
try:
for s in self.steps:
new_s = self.STR_RENDER(s)
if new_s != old_s:
old_s = new_s
yield new_s
except AttributeError:
pass
# Lui même
new_s = self.STR_RENDER(self.postfix_tokens)
if new_s != old_s:
yield new_s
# -----------------------------
# Reglages pour 'vim'
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
# cursor: 16 del

View File

@ -2,17 +2,16 @@
# encoding: utf-8 # encoding: utf-8
from .generic import Stack, flatten_list, expand_list, isNumber, isOperator, isNumerand from .generic import Stack, flatten_list, expand_list, isNumber, isOperator, isNumerand
from .render import txt, tex
from .str2tokens import str2tokens from .str2tokens import str2tokens
from .operator import op from .operator import op
from .render import txt, tex
from .explicable import Explicable
from .random_expression import RdExpression from .random_expression import RdExpression
__all__ = ['Expression'] __all__ = ['Expression', 'Renderable']
class Expression(object):
"""A calculus expression. Today it can andle only expression with numbers later it will be able to manipulate unknown"""
class Renderable(object):
STR_RENDER = tex STR_RENDER = tex
DEFAULT_RENDER = tex DEFAULT_RENDER = tex
@ -37,23 +36,23 @@ class Expression(object):
>>> exp = Expression("2*3/5") >>> exp = Expression("2*3/5")
>>> print(exp) >>> print(exp)
\\frac{ 2 \\times 3 }{ 5 } \\frac{ 2 \\times 3 }{ 5 }
>>> for i in exp.simplify(): >>> for i in exp.simplify().explain():
... print(i) ... print(i)
\\frac{ 2 \\times 3 }{ 5 } \\frac{ 2 \\times 3 }{ 5 }
\\frac{ 6 }{ 5 } \\frac{ 6 }{ 5 }
>>> with Expression.tmp_render(): >>> with Expression.tmp_render():
... for i in exp.simplify(): ... for i in exp.simplify().explain():
... i ... i
< Expression [2, 3, '*', 5, '/']> < Expression [2, 3, '*', 5, '/']>
< Expression [6, 5, '/']> < Expression [6, 5, '/']>
< Fraction 6 / 5> < Fraction 6 / 5>
>>> with Expression.tmp_render(txt): >>> with Expression.tmp_render(txt):
... for i in exp.simplify(): ... for i in exp.simplify().explain():
... print(i) ... print(i)
2 * 3 / 5 2 * 3 / 5
6 / 5 6 / 5
>>> for i in exp.simplify(): >>> for i in exp.simplify().explain():
... print(i) ... print(i)
\\frac{ 2 \\times 3 }{ 5 } \\frac{ 2 \\times 3 }{ 5 }
\\frac{ 6 }{ 5 } \\frac{ 6 }{ 5 }
@ -68,6 +67,11 @@ class Expression(object):
Expression.set_render(self.old_render) Expression.set_render(self.old_render)
return TmpRenderEnv() return TmpRenderEnv()
class Expression(Explicable, Renderable):
"""A calculus expression. Today it can andle only expression with numbers later it will be able to manipulate unknown"""
@classmethod @classmethod
def random(self, form="", conditions=[], val_min = -10, val_max=10): def random(self, form="", conditions=[], val_min = -10, val_max=10):
"""Create a random expression from form and with conditions """Create a random expression from form and with conditions
@ -89,8 +93,7 @@ class Expression(object):
""" """
expression = object.__new__(cls) expression = object.__new__(cls)
if type(exp) == str: if type(exp) == str:
#self._exp = exp expression.postfix_tokens = str2tokens(exp)
expression.postfix_tokens = str2tokens(exp) # les tokens seront alors stockés dans self.tokens temporairement
elif type(exp) == list: elif type(exp) == list:
expression.postfix_tokens = flatten_list([tok.postfix_tokens if Expression.isExpression(tok) else tok for tok in exp]) expression.postfix_tokens = flatten_list([tok.postfix_tokens if Expression.isExpression(tok) else tok for tok in exp])
else: else:
@ -98,25 +101,24 @@ class Expression(object):
if len(expression.postfix_tokens) == 1: if len(expression.postfix_tokens) == 1:
token = expression.postfix_tokens[0] token = expression.postfix_tokens[0]
if hasattr(token, 'simplify'): if hasattr(token, 'simplify') and hasattr(token, 'explain'):
return expression.postfix_tokens[0] return expression.postfix_tokens[0]
elif type(token) == int: elif type(token) == int:
# On crée un faux int en ajoutant la méthode simplify et simplified et la caractérisique isNumber # On crée un faux int en ajoutant la méthode simplify et simplified et la caractérisique isNumber
simplify = lambda x:[x] simplify = lambda x:x
simplified = lambda x:x
is_number = True is_number = True
methods_attr = {'simplify':simplify, 'simplified':simplified, 'isNumber': is_number} methods_attr = {'simplify':simplify, 'isNumber': is_number, 'postfix_tokens': [token]}
fake_token = type('fake_int', (int,), methods_attr)(token) fake_token = type('fake_int', (int,Explicable, Renderable), methods_attr)(token)
return fake_token return fake_token
elif type(token) == str: elif type(token) == str:
# TODO: Pourquoi ne pas créer directement un polynom ici? |jeu. févr. 26 18:59:24 CET 2015
# On crée un faux str en ajoutant la méthode simplify et simplified et la caractérisique isNumber # On crée un faux str en ajoutant la méthode simplify et simplified et la caractérisique isNumber
simplify = lambda x:[x] simplify = lambda x:[x]
simplified = lambda x:x
is_polynom = True is_polynom = True
methods_attr = {'simplify':simplify, 'simplified':simplified, '_isPolynom': is_polynom} methods_attr = {'simplify':simplify, '_isPolynom': is_polynom, 'postfix_tokens': [token]}
fake_token = type('fake_str', (str,), methods_attr)(token) fake_token = type('fake_str', (str,Explicable, Renderable), methods_attr)(token)
return fake_token return fake_token
else: else:
@ -129,74 +131,98 @@ class Expression(object):
def __str__(self): def __str__(self):
""" """
Overload str Overload str
If you want to changer render use Expression.set_render(...)
If you want to changer render use Expression.set_render(...) or use tmp_render context manager.
""" """
return self.STR_RENDER(self.postfix_tokens) return self.STR_RENDER(self.postfix_tokens)
def __repr__(self): def __repr__(self):
return "< Expression " + str(self.postfix_tokens) + ">" return " ".join(["<", self.__class__ , str(self.postfix_tokens), ">"])
def render(self, render = lambda x:str(x)): #def __str__(self):
""" Same as __str__ but accept render as argument # """
:param render: function which render the list of token (postfix form) to string # Overload str
# If you want to changer render use Expression.set_render(...)
# """
# return self.STR_RENDER(self.postfix_tokens)
""" #def __repr__(self):
# TODO: I don't like the name of this method |ven. janv. 17 12:48:14 CET 2014 # return "< Expression " + str(self.postfix_tokens) + ">"
return 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): def simplify(self):
""" Generator which return steps for computing the expression """ """ Compute entirely the expression and return the result with .steps attribute """
if not self.can_go_further():
yield self.STR_RENDER(self.postfix_tokens)
else:
self.compute_exp() self.compute_exp()
old_s = ''
for s in self.steps:
new_s = self.STR_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
if Expression.isExpression(self.child): self.simplified = self.child.simplify()
for s in self.child.simplify():
if old_s != s:
old_s = s
yield s
else:
for s in self.child.simplify():
new_s = self.STR_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
if old_s != self.STR_RENDER([self.child]):
yield self.STR_RENDER([self.child])
def simplified(self):
""" Get the simplified version of the expression """
self.compute_exp()
try: try:
return self.child.simplified() self.simplified.steps = self.child.steps + self.simplified.steps
except AttributeError: except AttributeError:
return self.child pass
def can_go_further(self): return self.simplified
"""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
""" # TODO: À changer |jeu. févr. 26 17:18:49 CET 2015
if len(self.postfix_tokens) == 1: # if not self.can_go_further():
return 0 # yield self.STR_RENDER(self.postfix_tokens)
else: # else:
return 1 # self.compute_exp()
# old_s = ''
# for s in self.steps:
# new_s = self.STR_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
# if Expression.isExpression(self.child):
# for s in self.child.simplify():
# if old_s != s:
# old_s = s
# yield s
# else:
# for s in self.child.simplify():
# new_s = self.STR_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
# if old_s != self.STR_RENDER([self.child]):
# yield self.STR_RENDER([self.child])
#def simplified(self):
# """ Get the simplified version of the expression """
# self.compute_exp()
# try:
# return self.child.simplified()
# except AttributeError:
# return self.child
# TODO: Normalement ne devrait plus être necessaire. Il faudra par contre s'assurer qu'il soit impossible de créer des Expressions avec une seul élément |jeu. févr. 26 17:26:28 CET 2015
# 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.postfix_tokens) == 1:
# return 0
# else:
# return 1
def compute_exp(self): def compute_exp(self):
""" Create self.child with self.steps to go up to it """ """ Create self.child with and stock steps in it """
self.steps = [self.postfix_tokens] child_steps = [self.postfix_tokens]
tokenList = self.postfix_tokens.copy() tokenList = self.postfix_tokens.copy()
tmpTokenList = [] tmpTokenList = []
@ -256,9 +282,10 @@ class Expression(object):
steps = expand_list(tmpTokenList) steps = expand_list(tmpTokenList)
if len(steps[:-1]) > 0: if len(steps[:-1]) > 0:
self.steps += [flatten_list(s) for s in steps[:-1]] child_steps += [flatten_list(s) for s in steps[:-1]]
self.child = Expression(steps[-1]) self.child = Expression(steps[-1])
self.child.steps = child_steps
@classmethod @classmethod
def isExpression(self, other): def isExpression(self, other):
@ -329,9 +356,10 @@ class Expression(object):
def test(exp): def test(exp):
a = Expression(exp) a = Expression(exp)
print(a) b = a.simplify()
for i in a.simplify():
print(type(i)) for i in b.explain():
#print(type(i))
print(i) print(i)
#print(type(a.simplified()), ":", a.simplified()) #print(type(a.simplified()), ":", a.simplified())
@ -366,32 +394,32 @@ if __name__ == '__main__':
#f = -e #f = -e
#print(f) #print(f)
#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)
#
#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" exp = "2 * ( 2 - ( 3 + 4 ) ) + ( 3 - 4 ) * 5"
#test(exp) test(exp)
#exp = "( 2 + 5 ) * ( 3 * 4 )" exp = "2 * ( 2 - ( 3 + 4 ) ) + 5 * ( 3 - 4 )"
#test(exp) 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 )" #exp = "( 2 + 5 - 1 ) / ( 3 * 4 )"
#test(exp) #test(exp)
@ -426,8 +454,8 @@ if __name__ == '__main__':
#for i in exp.simplify(): #for i in exp.simplify():
# print(i) # print(i)
import doctest #import doctest
doctest.testmod() #doctest.testmod()
# ----------------------------- # -----------------------------
# Reglages pour 'vim' # Reglages pour 'vim'