Created Exp in operator_file.py to represent Power
Implemented Expand and regroup
This commit is contained in:
parent
fa1f19f5cc
commit
8ee24d763b
8 changed files with 335 additions and 58 deletions
|
@ -2,12 +2,11 @@ from __future__ import annotations
|
|||
|
||||
import python_symb.MathTypes.symbols
|
||||
from python_symb.Expressions.tree import Tree
|
||||
from python_symb.MathTypes.operator_file import Add, Mul, Min
|
||||
|
||||
from python_symb.MathTypes.operator_file import UnaryOperator, BinOperator, Add, Mul, Min
|
||||
from python_symb.MathTypes.operator_file import UnaryOperator, BinOperator, Add, Mul, Exp
|
||||
|
||||
from typing import List
|
||||
from python_symb.MathTypes.symbols import Var
|
||||
from python_symb.MathTypes.symbols import Var, var
|
||||
|
||||
from python_symb.Parsing.parse import infix_str_to_postfix
|
||||
|
||||
|
@ -24,9 +23,20 @@ class Expr(Tree):
|
|||
Expr(Mul, [Expr(5), Expr(Expr(Add, [Expr(2), Expr(3)]))])
|
||||
|
||||
"""
|
||||
__match_args__ = ('value', 'children')
|
||||
|
||||
def __init__(self, value, children=None):
|
||||
from python_symb.MathTypes.operator_file import BinOperator, UnaryOperator
|
||||
super().__init__(value, children if children else [])
|
||||
assert all([isinstance(child, Expr) for child in self.children]), f'Invalid children: {self.children} all child should be Expr'
|
||||
|
||||
match value:
|
||||
case BinOperator() as op:
|
||||
assert len(self.children) == 2, f'Invalid number of children for BinOperator{op}: {len(self.children)}'
|
||||
|
||||
case UnaryOperator() as op:
|
||||
assert len(self.children) == 1, f'Invalid number of children for UnaryOperator{op}: {len(self.children)}'
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_postfix_list(postfix: List):
|
||||
|
@ -60,6 +70,31 @@ class Expr(Tree):
|
|||
expr_rev_polish = infix_str_to_postfix(expr_str)
|
||||
return Expr.from_postfix_list(expr_rev_polish)
|
||||
|
||||
|
||||
def to_infix_str(self, parent_precedence=-100, implicit_mul=True) -> str:
|
||||
"""
|
||||
Return the infix string of the expression
|
||||
"""
|
||||
match self:
|
||||
case Expr(value) if self.is_leaf:
|
||||
return str(value)
|
||||
|
||||
case Expr(UnaryOperator() as op, [child]):
|
||||
return f"{op.name}({child.to_infix_str(parent_precedence=op.precedence)})"
|
||||
|
||||
case Expr(BinOperator() as op, [left, right]):
|
||||
op_name = op.name if not(implicit_mul and op == Mul) else ''
|
||||
if op.precedence < parent_precedence:
|
||||
print("hehehe")
|
||||
print(self, op.precedence, parent_precedence)
|
||||
return f"({left.to_infix_str(op.precedence)}{op_name}{right.to_infix_str(op.precedence)})"
|
||||
else:
|
||||
return f"{left.to_infix_str(op.precedence)}{op_name}{right.to_infix_str(op.precedence)}"
|
||||
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def bin_op_constructor(self, other, op):
|
||||
"""
|
||||
Construct a binary operation
|
||||
|
@ -73,32 +108,99 @@ class Expr(Tree):
|
|||
return ValueError(f'Invalid type for operation: {other} : {type(other)}')
|
||||
|
||||
def __add__(self, other):
|
||||
return self.bin_op_constructor(other, Add)
|
||||
other_expr = other if isinstance(other, Expr) else Expr(other)
|
||||
return Expr.bin_op_constructor(self, other_expr, Add)
|
||||
|
||||
def __radd__(self, other):
|
||||
other_expr = other if isinstance(other, Expr) else Expr(other)
|
||||
return Expr.bin_op_constructor(other_expr, self, Add)
|
||||
|
||||
def __mul__(self, other):
|
||||
return self.bin_op_constructor(other, Mul)
|
||||
other_expr = other if isinstance(other, Expr) else Expr(other)
|
||||
return Expr.bin_op_constructor(self, other_expr, Mul)
|
||||
|
||||
def __rmul__(self, other):
|
||||
other_expr = other if isinstance(other, Expr) else Expr(other)
|
||||
return Expr.bin_op_constructor(other_expr, self, Mul)
|
||||
|
||||
def __sub__(self, other):
|
||||
return self.bin_op_constructor(other, Min)
|
||||
return Expr.bin_op_constructor(self, other, Min)
|
||||
|
||||
def __hash__(self):
|
||||
"""
|
||||
Two equivalent expressions (without more modification like factorisation, or expanding) should have the same hash
|
||||
see test_eq
|
||||
"""
|
||||
match self:
|
||||
|
||||
case Expr(value) if self.is_leaf:
|
||||
return hash(value)
|
||||
|
||||
case Expr(UnaryOperator() as op, [child]):
|
||||
return hash(op.name + str(hash(child)))
|
||||
|
||||
case Expr(BinOperator() as op, [left, right]):
|
||||
if op.properties.commutative and op.properties.associative:
|
||||
return hash(op.name) + hash(left) + hash(right)
|
||||
else:
|
||||
return hash(op.name) + hash(str(hash(left)) + str(hash(right)))
|
||||
|
||||
case _:
|
||||
print(f'Invalid type: {type(self)}')
|
||||
|
||||
def bad_eq(self, other):
|
||||
return self.__hash__() == other.__hash__()
|
||||
|
||||
def __eq__(self, other):
|
||||
"""temporary"""
|
||||
return self.bad_eq(other)
|
||||
|
||||
|
||||
def test1():
|
||||
x, y = Var('x'), Var('y')
|
||||
expr1 = x + y
|
||||
expr2 = 5+x
|
||||
print(expr1 + expr2)
|
||||
def test():
|
||||
x, y = var('x'), var('y')
|
||||
a, b = var('a'), var('b')
|
||||
def test1():
|
||||
expr1 = x + y
|
||||
expr2 = 5+x
|
||||
print(expr1 + expr2)
|
||||
|
||||
|
||||
def test2():
|
||||
from python_symb.MathTypes.operator_file import Sin
|
||||
a, b = Var('a'), Var('b')
|
||||
expr = Sin(a+b)
|
||||
print(expr)
|
||||
def test2():
|
||||
from python_symb.MathTypes.operator_file import Sin
|
||||
expr = Sin(x+y)
|
||||
print(expr)
|
||||
|
||||
def test_eq():
|
||||
expr = x + y + 3
|
||||
expr2 = 3 + x + y
|
||||
print("----")
|
||||
print(expr)
|
||||
print(expr2)
|
||||
print(expr == expr2)
|
||||
|
||||
def test_return_to_string():
|
||||
expr = x+y
|
||||
new_expr = 5*expr
|
||||
print(new_expr)
|
||||
print(f"new_expr: {new_expr.to_infix_str()}")
|
||||
|
||||
expr2 = x*x*x + y*y*y
|
||||
print(expr2)
|
||||
print(f"expr2: {expr2.to_infix_str()}")
|
||||
|
||||
|
||||
print("test1")
|
||||
test1()
|
||||
print("test2")
|
||||
test2()
|
||||
print("test_eq")
|
||||
test_eq()
|
||||
print("test_return_to_string")
|
||||
test_return_to_string()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test1()
|
||||
test2()
|
||||
test()
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -82,4 +82,7 @@ class Tree(ABC):
|
|||
yield from aux(child)
|
||||
yield tree
|
||||
|
||||
yield from aux(self)
|
||||
yield from aux(self)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from __future__ import annotations
|
||||
from typing import Dict, Callable
|
||||
from typing import Set, Callable
|
||||
from python_symb.MathTypes.symbols import Symbols
|
||||
|
||||
|
||||
|
@ -52,11 +52,11 @@ class BinProperties:
|
|||
Represent the properties of a binary operator
|
||||
"""
|
||||
|
||||
def __init__(self, associativity: bool, commutativity: True,
|
||||
left_distributivity: Dict[str, bool], right_distributivity: Dict[str, bool]):
|
||||
def __init__(self, associative: bool, commutative: True,
|
||||
left_distributivity: Set[str], right_distributivity: Set[str]):
|
||||
"""
|
||||
:param associativity: True if the operator is associative
|
||||
:param commutativity: True if the operator is commutative
|
||||
:param associative: True if the operator is associative
|
||||
:param commutative: True if the operator is commutative
|
||||
:param left_distributivity: a dictionary of the operators that the current operator distribute over
|
||||
:param right_distributivity: a dictionary of the operators that distribute over the current operator
|
||||
|
||||
|
@ -68,10 +68,10 @@ class BinProperties:
|
|||
right_distributivity = {'+': True}
|
||||
"""
|
||||
|
||||
self.associativity = associativity
|
||||
self.commutativity = commutativity
|
||||
self.left_distributivity = left_distributivity
|
||||
self.right_distributivity = right_distributivity
|
||||
self.associative = associative
|
||||
self.commutative = commutative
|
||||
self.left_distributive = left_distributivity
|
||||
self.right_distributive = right_distributivity
|
||||
|
||||
|
||||
class BinOperator(Operator):
|
||||
|
@ -92,18 +92,26 @@ class BinOperator(Operator):
|
|||
return self.call(left, right)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
"""
|
||||
Generic operators
|
||||
"""
|
||||
ExpProperties = BinProperties(False, False, set(), set())
|
||||
Exp = BinOperator('^', 4, ExpProperties, lambda x, y: x ** y)
|
||||
|
||||
MulProperties = BinProperties(True, True, {'+'}, {'+'})
|
||||
Mul = BinOperator('*', 3, MulProperties, lambda x, y: x * y, Exp)
|
||||
|
||||
AddProperties = BinProperties(True, True, set(), set())
|
||||
Add = BinOperator('+', 2, AddProperties, lambda x, y: x + y, Mul)
|
||||
|
||||
|
||||
|
||||
AddProperties = BinProperties(True, True, {'*': True}, {'*': True})
|
||||
Add = BinOperator('+', 2, AddProperties, lambda x, y: x + y)
|
||||
|
||||
|
||||
MulProperties = BinProperties(True, True, {'+': True}, {'+': True})
|
||||
Mul = BinOperator('*', 3, MulProperties, lambda x, y: x * y)
|
||||
Sin = UnaryOperator('sin', 10, lambda x: x)
|
||||
|
||||
Min = BinOperator('-', 2, AddProperties, lambda x, y: x - y)
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
from __future__ import annotations
|
||||
import traceback
|
||||
|
||||
|
||||
|
||||
class Symbols:
|
||||
|
@ -18,21 +20,33 @@ class Symbols:
|
|||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def __add__(self, other):
|
||||
def __add__(self, other) -> Expr:
|
||||
from python_symb.Expressions.expr import Expr
|
||||
return Expr('+', [self, other])
|
||||
from python_symb.MathTypes.operator_file import Add
|
||||
other_expr = other if isinstance(other, Expr) else Expr(other)
|
||||
self_expr = Expr(self)
|
||||
return Expr(Add, [self_expr, other_expr])
|
||||
|
||||
def __radd__(self, other):
|
||||
def __radd__(self, other) -> Expr:
|
||||
from python_symb.Expressions.expr import Expr
|
||||
return Expr('+', [other, self])
|
||||
from python_symb.MathTypes.operator_file import Add
|
||||
other_expr = other if isinstance(other, Expr) else Expr(other)
|
||||
self_expr = Expr(self)
|
||||
return Expr(Add, [other_expr, self_expr])
|
||||
|
||||
def __mul__(self, other):
|
||||
def __mul__(self, other) -> Expr:
|
||||
from python_symb.Expressions.expr import Expr
|
||||
return Expr('*', [self, other])
|
||||
from python_symb.MathTypes.operator_file import Mul
|
||||
other_expr = other if isinstance(other, Expr) else Expr(other)
|
||||
self_expr = Expr(self)
|
||||
return Expr(Mul, [self_expr, other_expr])
|
||||
|
||||
def __rmul__(self, other):
|
||||
def __rmul__(self, other) -> Expr:
|
||||
from python_symb.Expressions.expr import Expr
|
||||
return Expr('*', [other, self])
|
||||
from python_symb.MathTypes.operator_file import Mul
|
||||
other_expr = other if isinstance(other, Expr) else Expr(other)
|
||||
self_expr = Expr(self)
|
||||
return Expr(Mul, [other_expr, self_expr])
|
||||
|
||||
|
||||
class Var(Symbols):
|
||||
|
@ -45,4 +59,15 @@ class Var(Symbols):
|
|||
super().__init__(name)
|
||||
self.__class__.instances[name] = self
|
||||
|
||||
def to_expr(self):
|
||||
from python_symb.Expressions.expr import Expr
|
||||
return Expr(self)
|
||||
|
||||
|
||||
def var(name: str) -> Expr:
|
||||
"""
|
||||
Create a variable, return Expr
|
||||
"""
|
||||
from python_symb.Expressions.expr import Expr
|
||||
v = Var(name)
|
||||
return Expr(v)
|
||||
|
|
|
@ -4,7 +4,6 @@ from python_symb.MathTypes.operator_file import BinOperator, UnaryOperator
|
|||
from python_symb.MathTypes.symbols import Symbols, Var
|
||||
from python_symb.MathTypes.fraction import Fraction
|
||||
|
||||
x, y = Var('x'), Var('y')
|
||||
|
||||
ParenthesisLeft = Symbols('(')
|
||||
ParenthesisRight = Symbols(')')
|
||||
|
@ -103,7 +102,7 @@ def infix_str_to_postfix(expr):
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
x, y = Var('x'), Var('y')
|
||||
expr = "(x+7)*y+sin(24-2*(1-5))"
|
||||
prep = preprocess(expr)
|
||||
print(prep)
|
||||
|
|
153
python_symb/TreeModification/basic_modif.py
Normal file
153
python_symb/TreeModification/basic_modif.py
Normal file
|
@ -0,0 +1,153 @@
|
|||
from python_symb.Expressions.expr import Expr
|
||||
from python_symb.MathTypes.symbols import Var
|
||||
from python_symb.MathTypes.operator_file import Operator, BinOperator, Add, Mul
|
||||
from typing import Union
|
||||
|
||||
|
||||
Number = Union[int, float]
|
||||
|
||||
|
||||
def expand(expr: Expr) -> Expr:
|
||||
"""
|
||||
Expand an expression
|
||||
|
||||
:param expr: expression to expand
|
||||
:return: expanded expression
|
||||
|
||||
example :
|
||||
5*(a+b) -> 5*a + 5*b
|
||||
Expr(Mul, [5, Expr(Add, [Expr(a), Expr(b)])]) -> Expr(Add, [Expr(Mul, [5, Expr(a)]), Expr(Mul, [5, Expr(b)])])
|
||||
"""
|
||||
|
||||
if expr.is_leaf:
|
||||
return expr
|
||||
|
||||
match expr:
|
||||
case Expr(BinOperator() as Op1, [Expr(Op2, op2_children), right]) if Op2.name in Op1.properties.left_distributive:
|
||||
return expand(Expr(Op2, [Expr(Op1, [expand(op2_child), expand(right)]) for op2_child in op2_children]))
|
||||
|
||||
case Expr(BinOperator() as Op1, [left, Expr(Op2, op2_children)]) if Op2.name in Op1.properties.right_distributive:
|
||||
return expand(Expr(Op2, [Expr(Op1, [expand(left), expand(op2_child)]) for op2_child in op2_children]))
|
||||
|
||||
case Expr(BinOperator() as Op, [left, right]):
|
||||
return Expr(Op, [expand(left), expand(right)])
|
||||
|
||||
return expr
|
||||
|
||||
|
||||
def _regroup(expr: Expr, focus_op: BinOperator) -> Expr:
|
||||
"""
|
||||
regroup an expression, with the contraint that the value of expr is focus_op
|
||||
Will be used to regroup an expression
|
||||
|
||||
:param expr: expression to regroup
|
||||
:param focus_op: operator to regroup
|
||||
:return
|
||||
x+x+x+x -> 4*x
|
||||
Expr(Add, [Expr(x), Expr(Add, [Expr(x), Expr(Add, [Expr(x), Expr(x)])])]) -> Expr(Mul, [4, Expr(x)])
|
||||
|
||||
with Mul == Add.repeated_op
|
||||
"""
|
||||
assert focus_op.repeated_op is not None, f'{focus_op} has no repeated_op'
|
||||
assert expr.value == focus_op, f'{expr.value} is not a {focus_op}'
|
||||
|
||||
# Motifs : Key : (Expr) -> Value : int
|
||||
# represent number of times the expression appears in the expression,
|
||||
# custom hash make for instance x+y and y+x the same when counting
|
||||
motifs = {}
|
||||
|
||||
def collect_motifs(expr: Expr):
|
||||
match expr:
|
||||
case Expr(BinOperator() as op, [left, right]) if op == focus_op:
|
||||
collect_motifs(left)
|
||||
collect_motifs(right)
|
||||
case Expr(BinOperator() as op, [left, right]) if op == focus_op.repeated_op and isinstance(right.value, Number):
|
||||
motifs[left] = motifs.get(expr, 0) + right.value
|
||||
case Expr(BinOperator() as op, [left, right]) if op == focus_op.repeated_op and op.properties.commutative and isinstance(left.value, Number):
|
||||
|
||||
motifs[right] = 1 if right not in motifs else motifs[right] + left.value
|
||||
|
||||
case _:
|
||||
motifs[expr] = 1 if expr not in motifs else motifs[expr] + 1
|
||||
|
||||
collect_motifs(expr)
|
||||
tuple_motifs = list(motifs.items())
|
||||
|
||||
def reconstruct(tuple_motifs):
|
||||
match tuple_motifs:
|
||||
case [(expr, int(a))]:
|
||||
if focus_op.repeated_op.properties.commutative:
|
||||
return Expr(focus_op.repeated_op, [Expr(a), expr])
|
||||
return Expr(focus_op.repeated_op, [expr, Expr(a)])
|
||||
case [(expr, int(a)), *rest]:
|
||||
if focus_op.repeated_op.properties.commutative:
|
||||
return Expr(focus_op, [Expr(focus_op.repeated_op, [Expr(a), expr]), reconstruct(rest)])
|
||||
return Expr(focus_op, [Expr(focus_op.repeated_op, [expr, Expr(a)]), reconstruct(rest)])
|
||||
|
||||
return reconstruct(tuple_motifs)
|
||||
|
||||
def regroup(expr: Expr, focus_op: BinOperator) -> Expr:
|
||||
"""
|
||||
Regroup an expression
|
||||
|
||||
:param expr: expression to regroup
|
||||
:param focus_op: operator to regroup
|
||||
:return: regrouped expression
|
||||
|
||||
example :
|
||||
x+x+x+x -> 4*x
|
||||
Expr(Add, [Expr(x), Expr(Add, [Expr(x), Expr(Add, [Expr(x), Expr(x)])])]) -> Expr(Mul, [4, Expr(x)])
|
||||
"""
|
||||
|
||||
if expr.is_leaf:
|
||||
return expr
|
||||
|
||||
match expr:
|
||||
case Expr(BinOperator() as op, [left, right]) if op == focus_op:
|
||||
return _regroup(expr, focus_op)
|
||||
|
||||
case Expr(BinOperator() as op, [left, right]):
|
||||
return Expr(op, [regroup(left, focus_op), regroup(right, focus_op)])
|
||||
|
||||
return expr
|
||||
|
||||
|
||||
def test():
|
||||
|
||||
x, y = Var('x'), Var('y')
|
||||
a, b = Var('a'), Var('b')
|
||||
|
||||
def test_expand():
|
||||
expr = (x+y)*(x+y)*(x+y)
|
||||
expr = expand(expr)
|
||||
expr = regroup(expr, Add)
|
||||
print(f"(x+y)*(x+y)*(x+y) -> {expr.to_infix_str()}")
|
||||
expr = (x+y+a)*b
|
||||
print(f"(x+y+a)*b -> {expand(expr).to_infix_str()}")
|
||||
|
||||
def test_regroup():
|
||||
expr = x+2*x+y+y+2*y
|
||||
print(f"x+2*x+y+y+2*y -> {regroup(expr, Add).to_infix_str()}")
|
||||
|
||||
def test_power():
|
||||
expr = x*x*x + y*y*y
|
||||
print(f"x*x*x -> {regroup(expr, Mul).to_infix_str()}")
|
||||
|
||||
def test_all():
|
||||
expr = (x+y)*(x+y)*(x+y)
|
||||
expanded_expr = expand(expr)
|
||||
regrouped_expr = regroup(expanded_expr, Add)
|
||||
print(f"(x+y)*(x+y)*(x+y) -> {regrouped_expr.to_infix_str()}")
|
||||
|
||||
|
||||
|
||||
|
||||
test_expand()
|
||||
test_regroup()
|
||||
test_power()
|
||||
#test_all()
|
||||
|
||||
if __name__ == "__main__":
|
||||
test()
|
||||
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
from python_symb.Expressions.expr import Expr
|
||||
from python_symb.MathTypes.symbols import Var
|
||||
from python_symb.MathTypes.operator_file import Operator, BinOperator, Add, Mul
|
||||
|
||||
def expand(expr: Expr) -> Expr:
|
||||
"""
|
||||
Expand an expression
|
||||
:param expr: expression to expand
|
||||
:return: expanded expression
|
||||
"""
|
||||
|
||||
if expr.is_leaf:
|
||||
return expr
|
||||
|
|
@ -109,6 +109,7 @@ class Visual:
|
|||
self.create_circle(first_x, first_y, radius, "red")
|
||||
self.tree_canvas.create_text(first_x, first_y, text=str(tree.value))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
Visual()
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue