Mapytex/pymath/operator.py
2015-06-19 11:01:13 +02:00

641 lines
18 KiB
Python

#!/usr/bin/env python
# encoding: utf-8
from .generic import flatten_list, isNumber
from functools import wraps
import types
class Operator(str):
"""The operator class, is a string (representation of the operator) with its arity"""
def __new__(cls, operator = "", name = "", priority = 0, actions = ("",""), txt = "", tex = "", arity = 2):
""" Create an Operator """
#def __new__(cls, operator, arity = 2):
op = str.__new__(cls, operator)
op.operator = operator
op.name = name
op.arity = arity
op.priority = priority
op.actions = actions
op.txt = txt
op.tex = tex
op.isOperator = 1
# TODO: Add self.visibility |sam. nov. 8 17:00:08 CET 2014
return op
def __call__(self, *args):
""" Calling this operator performs the rigth calculus """
return self._call(*args)
def _call(self, *args):
"""Trick to avoid overloading __call__ """
if self.arity == 1:
return getattr(args[0], self.actions)()
elif self.arity == 2:
if type(args[1]) == int:
return getattr(args[0], self.actions[0])(args[1])
else:
return getattr(args[1], self.actions[1])(args[0])
def _render(self, link, *args):
"""Global step for __txt__ and __tex__
:param link: the link between operators
:param *args: the operands
:returns: the string with operator and operands
"""
if self.arity == 1:
op1 = self.l_parenthesis(args[0], True)
ans = link.format(op1 = op1)
elif self.arity == 2:
op1 = self.l_parenthesis(args[0], True)
op2 = self.r_parenthesis(args[1], True)
ans = link.format(op1 = op1, op2 = op2)
ans = save_mainOp(ans, self)
return ans
def __txt__(self, *args):
"""Txt rendering for the operator
:*args: Operands for this operation
:returns: String with operator and his operands
>>> op.mul.__txt__('1','2')
'1 * 2'
>>> op.add.__txt__('1','2')
'1 + 2'
>>> f = save_mainOp('2 + 3',op.add)
>>> op.mul.__txt__(f, '4')
'( 2 + 3 ) * 4'
>>> f = save_mainOp('-3',op.sub1)
>>> op.sub1.__txt__(f)
'- ( -3 )'
>>> op.sub1.__txt__('-3')
'- ( -3 )'
>>> f = save_mainOp('2 + 3',op.add)
>>> op.sub1.__txt__(f)
'- ( 2 + 3 )'
"""
return self._render(self.txt, *args)
def __tex__(self, *args):
"""Tex rendering for the operator
:*args: Operands for this operation
:returns: String with operator and his operands
>>> op.mul.__tex__('1','2')
'1 \\\\times 2'
>>> op.add.__tex__('1','2')
'1 + 2'
>>> f = save_mainOp('2 + 3',op.add)
>>> op.mul.__tex__(f, '4')
'( 2 + 3 ) \\\\times 4'
>>> f = save_mainOp('-3',op.sub1)
>>> op.sub1.__tex__(f)
'- ( -3 )'
>>> op.sub1.__tex__('-3')
'- ( -3 )'
>>> f = save_mainOp('2 + 3',op.add)
>>> op.sub1.__tex__(f)
'- ( 2 + 3 )'
"""
return self._render(self.tex, *args)
def __p2i__(self, *args):
"""Fix list transformation for the operator
:*args: Operands for this operation
:returns: list with the operator surrounded by operands
>>> op.mul.__p2i__(1,2)
[1, '*', 2]
>>> f = save_mainOp([2, op.add, 3],op.add)
>>> op.mul.__p2i__(f, 4)
['(', 2, '+', 3, ')', '*', 4]
>>> f = save_mainOp([op.sub1, 3],op.sub1)
>>> op.sub1.__p2i__(f)
['-', '(', '-', 3, ')']
>>> op.sub1.__p2i__(-3)
['-', '(', -3, ')']
>>> f = save_mainOp([2, op.add, 3],op.add)
>>> op.sub1.__p2i__(f)
['-', '(', 2, '+', 3, ')']
"""
if self.arity == 1:
op1 = self.l_parenthesis(args[0])
ans = flatten_list([self, op1])
elif self.arity == 2:
op1 = self.l_parenthesis(args[0])
op2 = self.r_parenthesis(args[1])
ans = flatten_list([op1, self, op2])
ans = save_mainOp(ans, self)
return ans
def l_parenthesis(self, opl, str_join=False):
""" Add parenthesis for left operand if necessary """
ans = opl
try:
# TODO: Je pige pas pourquoi quand on enlève .name ça marche plus... |lun. avril 27 19:07:24 CEST 2015
if opl.mainOp.name == op.sub1.name:
ans = opl
elif opl.mainOp.priority < self.priority:
ans = flatten_list(["(", opl, ")"])
except AttributeError as e:
# op has not the attribute priority
pass
ans = flatten_list([ans])
if str_join:
ans = ' '.join([str(i) for i in ans])
return ans
def r_parenthesis(self, op, str_join=False):
""" Add parenthesis for rigth operand if necessary """
try:
if op.mainOp.priority < self.priority:
op = flatten_list(["(", op, ")"])
except AttributeError:
# op has not the attribute priority
try:
if int(op) < 0:
op = ['(', op, ')']
except ValueError:
pass
ans = flatten_list([op])
if str_join:
ans = ' '.join([str(i) for i in ans])
return ans
def save_mainOp(obj, mainOp):
"""Create a temporary class build over built-in type to stock the main operation of a calculus
:obj: the object to add the attribute
:mainOp: the main operator
:returns: the same object with the main operation attribute
"""
Fake = type('fake_'+str(type(obj)), (type(obj),), {'mainOp': mainOp})
return Fake(obj)
def operatorize(fun):
"""Transform the answer of the function into an operator
The returned value of the function has to be a dictionnary with those keys
* "operator": the name (Needed!)
* "priority": the priority level
* "actions": mathematics actions of the operator (list of 1 element if the arity is 1, 2 elements if arity is 2)
* "txt": string ready to be formated in txt for with {op1} and/or {op2}
* "tex": string ready to be formated in tex for with {op1} and/or {op2}
* "arity": arity ie number of operands needed
* "_call": action to perform when call the operator
* "_render": action use in __txt__ and __tex__
* "__txt__": txt rendering
* "__tex__": tex rendering
* "l_parenthesis": mechanism to add parenthesis for left operande
* "r_parenthesis": mechanism to add parenthesis for rigth operande
"""
@wraps(fun)
def mod_fun(self, *args):
ans = fun(self, *args)
def _eq(op1, op2):
""" op1 == op2 """
return op1.name == op2.name == name and op1.arity == op2.arity
ans["__eq__"] = _eq
new_op = Operator(ans["operator"])
for (attr, value) in ans.items():
if hasattr(value, '__call__'):
setattr(new_op, attr, types.MethodType(value, new_op))
else:
setattr(new_op, attr, value)
return new_op
return mod_fun
class ClassProperty(object):
def __init__(self, fget):
self.fget = fget
def __get__(self, owner_self, owner_cls):
return self.fget(owner_cls)
class op(object):
""" List of admited operations """
_operators = {("+",2): "add",\
("-", 2): "sub",\
("-", 1): "sub1",\
("*", 2): "mul",\
("/", 2): "div",\
("^", 2): "pw",\
("(", 2): "par",\
}
@classmethod
def get_op(cls, op, arity = 2):
"""Return the corresponding operator
:op: symbole of the op
:arity: the arity
>>> op.get_op('+')
'+'
>>> mul = op.get_op('*')
>>> mul.tex
'{op1} \\\\times {op2}'
>>> mul.txt
'{op1} * {op2}'
"""
try:
return getattr(cls, cls._operators[(op, arity)])
except KeyError:
raise KeyError("{theOp} (arity: {arity}) is not available".format(theOp = op, arity = arity))
@classmethod
def can_be_operator(cls, symbole):
""" Tell if the symbole can be an operator """
if type(symbole) == str:
return symbole in [i[0] for i in cls._operators]
else:
return False
@ClassProperty
@operatorize
def add(cls):
""" The operator +
For doctest see test/test_operator.py
"""
def _render(self, link, *args):
"""Global step for __txt__ and __tex__
:param link: the link between operators
:param *args: the operands
:returns: the string with operator and operands
"""
if args[1][0] == "-":
op1 = self.l_parenthesis(args[0], True)
op2 = self.r_parenthesis(args[1][1:], True)
ans = link.replace('+','-').format(op1 = op1, op2 = op2)
ans = save_mainOp(ans, self)
return ans
else:
op1 = self.l_parenthesis(args[0], True)
op2 = self.r_parenthesis(args[1], True)
ans = link.format(op1 = op1, op2 = op2)
ans = save_mainOp(ans, self)
return ans
caract = {
"operator" : "+", \
"name" : "add",\
"priority" : 1, \
"arity" : 2, \
"actions" : ("__add__","__radd__"), \
"txt" : "{op1} + {op2}",\
"tex" : "{op1} + {op2}",\
"_render": _render,\
}
return caract
@ClassProperty
@operatorize
def sub(self):
""" The operator -
>>> sub = op.sub
>>> sub
'-'
>>> sub(1, 2)
-1
>>> sub.__tex__('1','2')
'1 - 2'
>>> sub.__txt__('1','2')
'1 - 2'
>>> sub.__tex__('1','-2')
'1 - (-2)'
>>> sub.__tex__('-1','2')
'-1 - 2'
"""
def l_parenthesis(self, op, str_join=False):
return op
def r_parenthesis(self, op, str_join=False):
try:
if op.mainOp.priority <= self.priority:
op = flatten_list(["(", op, ")"])
elif op[0] == '-':
op = flatten_list(["(", op, ")"])
except AttributeError:
# op has not the attribute priority
try:
if int(op) < 0:
op = ['(', op, ')']
except ValueError:
pass
ans = flatten_list([op])
if str_join:
ans = ' '.join([str(i) for i in ans])
return ans
caract = {
"operator" : "-", \
"name" : "sub",\
"priority" : 2, \
"arity" : 2, \
"actions" : ("__sub__","__rsub__"), \
"txt" : "{op1} - {op2}",\
"tex" : "{op1} - {op2}",\
"l_parenthesis": l_parenthesis,\
"r_parenthesis": r_parenthesis,\
}
return caract
@ClassProperty
@operatorize
def sub1(self):
""" The operator -
>>> sub1 = op.sub1
>>> sub1
'-'
>>> sub1(1)
-1
>>> sub1.__tex__('1')
'- 1'
>>> sub1.__txt__('1')
'- 1'
>>> sub1.__tex__('-1')
'- (-1)'
"""
def l_parenthesis(self, op, str_join=False):
""" Add parenthesis if necessary """
try:
if op.mainOp.priority <= self.priority:
op = flatten_list(["("] + [op] + [")"])
except AttributeError:
# op has not the attribute priority
try:
if int(op) < 0:
op = ['(', op, ')']
except ValueError:
pass
ans = flatten_list([op])
if str_join:
ans = ' '.join([str(i) for i in ans])
return ans
caract = {
"operator" : "-", \
"name" : "sub1",\
"priority" : 3, \
"arity" : 1, \
"actions" : "__neg__",\
"txt" : "- {op1}",\
"tex" : "- {op1}",\
"l_parenthesis": l_parenthesis,\
}
return caract
@ClassProperty
@operatorize
def mul(self):
""" The operator *
>>> mul = op.mul
>>> mul
'*'
>>> mul(1, 2)
2
>>> mul.__tex__('1','2')
'1 \\times 2'
>>> mul.__tex__('2','a')
'2 a'
>>> mul.__txt__('1','2')
'1 * 2'
>>> mul.__txt__('2','a')
'2 a'
>>> mul.__txt__('a','2')
'a * 2'
>>> mul.__tex__('1','-2')
'1 \\times (-2)'
"""
# * can not be display in some cases
def is_visible(self, op1, op2):
""" Tells whether self has to be visible or not
:param op1: left operande
:param op2: rigth operande
"""
# TODO: À finir!!! |lun. mars 9 00:03:40 CET 2015
if type(op2) == int:
# op2 est maintenant une chaine de caractères
return True
elif op2.isdecimal():
return True
elif op2.isalpha():
return False
elif op2[0].isdecimal():
return True
elif op2[0] == "(" and not ("+" in op2 or "-" in op2[3:]):
return True
# Giga bricolage...
elif "frac" in op2:
return True
else:
return False
def _render(self, link, *args):
op1 = self.l_parenthesis(args[0], True)
op2 = self.r_parenthesis(args[1], True)
if not self.is_visible(op1, op2):
ans = "{op1} {op2}".format(op1 = op1, op2 = op2)
else:
ans = link.format(op1 = op1, op2 = op2)
ans = save_mainOp(ans, self)
return ans
caract = {
"operator" : "*", \
"name" : "mul",\
"priority" : 4, \
"arity" : 2, \
"actions" : ("__mul__","__rmul__"), \
"txt" : "{op1} * {op2}",\
"tex" : "{op1} \\times {op2}",\
"visibility": 1,\
"_render": _render,\
"is_visible": is_visible,\
}
return caract
@ClassProperty
@operatorize
def div(self):
""" The operator /
>>> div = op.div
>>> div
'/'
>>> div(1, 2)
< Fraction 1 / 2>
>>> div.__tex__('1','2')
'\\frac{ 1 }{ 2 }'
>>> div.__tex__('1','2')
'\\frac{ -1 }{ 2 }'
>>> div.__txt__('1','2')
'1 / 2'
"""
from .fraction import Fraction
def _call(self, op1, op2):
if op2 == 1:
return op1
else:
return Fraction(op1,op2)
def __tex__(self, *args):
# Pas besoin de parenthèses en plus pour \frac
replacement = {"op"+str(i+1): op for (i,op) in enumerate(args)}
ans = self.tex.format(**replacement)
ans = save_mainOp(ans, self)
return ans
caract = {
"operator" : "/", \
"name" : "div",\
"priority" : 5, \
"arity" : 2, \
"txt" : "{op1} / {op2}",\
"tex" : "\\frac{{ {op1} }}{{ {op2} }}",\
"_call": _call,\
"__tex__":__tex__,\
}
return caract
@ClassProperty
@operatorize
def pw(self):
""" The operator ^
>>> pw = op.pw
>>> pw
'^'
>>> pw(2, 3)
8
>>> pw.__tex__('2','3')
'2^{ 3 }'
>>> pw.__txt__('2','3')
'2 ^ 3'
>>> pw.__txt__('-2','3')
'( -2 ) ^ 3'
"""
def _call(self, op1, op2):
""" Calling this operator performs the rigth calculus """
return getattr(op1, "__pow__")(op2)
def l_parenthesis(self, opl, str_join=False):
""" Add parenthesis for left operand if necessary """
ans = opl
try:
if opl.mainOp.priority < self.priority:
ans = flatten_list(["(", opl, ")"])
except AttributeError as e:
# op has not the attribute priority
pass
ans = flatten_list([ans])
if str_join:
ans = ' '.join([str(i) for i in ans])
return ans
caract = {
"operator" : "^", \
"name" : "pw",\
"priority" : 6, \
"arity" : 2, \
"actions" : ("__pow__",""), \
"txt" : "{op1} ^ {op2}",\
"tex" : "{op1}^{{ {op2} }}",\
"l_parenthesis": l_parenthesis,\
"_call":_call,\
}
return caract
@ClassProperty
@operatorize
def par(self):
""" The operator ( """
caract = {
"operator" : "(", \
"name" : "par",\
"priority" : 0, \
"arity" : 0, \
}
return caract
if __name__ == '__main__':
#print(op.add.__tex__('1','2'))
#print(op.mul.__tex__('1','2'))
#print(op.sub.__tex__('1','2'))
#f = save_mainOp('2 + 3',op.add)
#print(op.mul.__txt__(f, '4'))
#f = save_mainOp('-3',op.sub1)
#print(op.sub1.__txt__(f))
#print(op.sub1.__txt__('-3'))
#f = save_mainOp('2 + 3',op.add)
#print(op.sub1.__txt__(f))
#from .fraction import Fraction
#f = Fraction(1, 2)
#print(op.add.__txt__(f.__txt__(),'2'))
#print(op.add.__tex__(f.__tex__(),'2'))
#print("\t op.can_be_operator('+') :" + str(op.can_be_operator('+')))
#print("\t op.can_be_operator('t') :" + str(op.can_be_operator('t')))
print("op.sub.__dict__ -> ", op.sub.__dict__)
print(op.sub == op.sub1)
#import doctest
#doctest.testmod()
# -----------------------------
# Reglages pour 'vim'
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
# cursor: 16 del