diff --git a/python_symb/Expressions/expr.py b/python_symb/Expressions/expr.py new file mode 100644 index 0000000..8fb2922 --- /dev/null +++ b/python_symb/Expressions/expr.py @@ -0,0 +1,111 @@ +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 typing import List +from python_symb.MathTypes.symbols import Var + +from python_symb.Parsing.parse import infix_str_to_postfix + + +class Expr(Tree): + """ + A class to represent an expression tree + + value: the value of the node + children: the subtrees of the root (Default : None) + + exemple : + 5*(2+3) represented as + Expr(Mul, [Expr(5), Expr(Expr(Add, [Expr(2), Expr(3)]))]) + + """ + + def __init__(self, value, children=None): + super().__init__(value, children if children else []) + + @staticmethod + def from_postfix_list(postfix: List): + """ + Create an expression tree from a postfix list of tokens + + tokens are : int, float, Var, UnaryOperator, BinOperator + + exemple : + x = Var('x') + [5, 2, Add] -> Expr(Add, [Expr(5), Expr(2)]) + """ + + def aux(): + first = postfix.pop() + match first: + case int() | Var(): + return Expr(first) + case UnaryOperator(): + return Expr(first, [aux()]) + case BinOperator(): + return Expr(first, [aux(), aux()]) + + return aux() + + @staticmethod + def from_infix_str(expr_str) -> Expr: + """ + Create an expression tree from an infix string + """ + expr_rev_polish = infix_str_to_postfix(expr_str) + return Expr.from_postfix_list(expr_rev_polish) + + def bin_op_constructor(self, other, op): + """ + Construct a binary operation + """ + match other: + case Expr(): + return Expr(op, [self, other]) + case Var() | int() | float(): + return Expr(op, [self, Expr(other)]) + case _: + return ValueError(f'Invalid type for operation: {other} : {type(other)}') + + def __add__(self, other): + return self.bin_op_constructor(other, Add) + + def __mul__(self, other): + return self.bin_op_constructor(other, Mul) + + def __sub__(self, other): + return self.bin_op_constructor(other, Min) + + +def test1(): + x, y = Var('x'), Var('y') + 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) + + +if __name__ == '__main__': + test1() + test2() + + + + + + + + + + diff --git a/python_symb/tree.py b/python_symb/Expressions/tree.py similarity index 91% rename from python_symb/tree.py rename to python_symb/Expressions/tree.py index 6734322..c68f9a4 100644 --- a/python_symb/tree.py +++ b/python_symb/Expressions/tree.py @@ -1,14 +1,14 @@ from __future__ import annotations from typing import Iterable, Generator from collections import deque -from parse import update_symbols_dict +from abc import ABC, abstractmethod -class Tree: +class Tree(ABC): """ Ultra generic Test class. Can be used to represent any Test structure. - value : value of the node. Can be a binary operator like "+", a ternary operator like "if", a number etc... + :param value : value of the node. Can be a binary operator like "+", a ternary operator like "if", a number etc... depth_first_order : the default order of the node in the depth first traversal. Used to implement the depth_first method. 0 is pre-order, 1 is in-order (for binary Test), -1 is post-order. @@ -25,7 +25,7 @@ class Tree: self.children = children if children else [] def __repr__(self) -> str: - return f'Tree({self.value}, {self.children})' if self.children else f'Leaf({self.value})' + return f'Tree({self.value}, {self.children})' if self.children else f'Tree({self.value})' def height(self) -> int: return 1 + max((child.height() for child in self.children), default=0) diff --git a/python_symb/fraction.py b/python_symb/MathTypes/fraction.py similarity index 95% rename from python_symb/fraction.py rename to python_symb/MathTypes/fraction.py index 8f09354..3fa6939 100644 --- a/python_symb/fraction.py +++ b/python_symb/MathTypes/fraction.py @@ -1,11 +1,11 @@ from __future__ import annotations from typing import Iterable, Generator -from tools import gcd +from python_symb.IndependantTools.tools import gcd class Fraction: """ - Should represent a fraction not a division + Represent a fraction a/b with a and b elements of a ring (support +, -, *, /) """ __slots__ = ['num', 'den'] __match_args__ = ("num", "den") @@ -36,7 +36,7 @@ class Fraction: # can be completed with others objects that support gcd like polynomials etc... def simplify_to_num(self): - """from frac(a, 1) return a.""" + """from frac(a, 1) return a """ if self.den == 1: return self.num def simplify_nested(self, rec=True): diff --git a/python_symb/integers.py b/python_symb/MathTypes/integers.py similarity index 100% rename from python_symb/integers.py rename to python_symb/MathTypes/integers.py diff --git a/python_symb/operator_file.py b/python_symb/MathTypes/operator_file.py similarity index 51% rename from python_symb/operator_file.py rename to python_symb/MathTypes/operator_file.py index f04be55..86c779c 100644 --- a/python_symb/operator_file.py +++ b/python_symb/MathTypes/operator_file.py @@ -1,11 +1,20 @@ from __future__ import annotations from typing import Dict, Callable -from symbols import Symbols +from python_symb.MathTypes.symbols import Symbols class Operator(Symbols): + """ + Represent an operator, like +, *, sin, anything that can be applied to an expression + """ instances = [] - def __init__(self, name : str, precedence: int, call: Callable): + + def __init__(self, name: str, precedence: int, call: Callable): + """ + :param name of the operator + :param precedence: precedence of the operator, higher is better + :param call: function to apply the operator + """ super().__init__(name) self.precedence = precedence self.call = call @@ -16,20 +25,44 @@ class Operator(Symbols): class UnaryOperator(Operator): + """ + Represent a unary operator, like sin, cos, - etc... + all operators that take only one argument + """ instances = [] def __init__(self, name: str, precedence: int, call: Callable): UnaryOperator.instances.append(self) super().__init__(name, precedence, call) - def __call__(self, expr): + def apply(self, expr): return self.call(expr) + def __call__(self, e): + from python_symb.Expressions.expr import Expr + return Expr(self, [e]) + 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]): + """ + :param associativity: True if the operator is associative + :param commutativity: 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 + + exemple: + for the operator *: + associativity = True + commutativity = True + left_distributivity = {'+': True} + right_distributivity = {'+': True} + """ self.associativity = associativity self.commutativity = commutativity @@ -38,6 +71,12 @@ class BinProperties: class BinOperator(Operator): + """ + Represent a binary operator, like +, *, etc... + all operators that take two arguments + """ + + # Used to store all the instances of BinOperator, used in the parser instances = [] def __init__(self, name: str, precedence: int, properties: BinProperties, call: Callable): @@ -45,10 +84,14 @@ class BinOperator(Operator): super().__init__(name, precedence, call) self.properties = properties - def __call__(self, left, right): + def apply(self, left, right): return self.call(left, right) +""" +Generic operators +""" + AddProperties = BinProperties(True, True, {'*': True}, {'*': True}) Add = BinOperator('+', 2, AddProperties, lambda x, y: x + y) @@ -58,5 +101,5 @@ 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) -# Pns = UnaryOperator('()', 0, lambda x: x) + diff --git a/python_symb/parse.py b/python_symb/Parsing/parse.py similarity index 93% rename from python_symb/parse.py rename to python_symb/Parsing/parse.py index c237c5a..5d374dd 100644 --- a/python_symb/parse.py +++ b/python_symb/Parsing/parse.py @@ -1,8 +1,8 @@ from __future__ import annotations from typing import List, Union -from operator_file import Add, Mul, Min, BinOperator, UnaryOperator -from symbols import Symbols, Var -from fraction import Fraction +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') @@ -37,6 +37,7 @@ def preprocess(expr: str) -> List: :param expr: string expression :return: list of symbols and numbers """ + update_symbols_dict() return_list = [] expr = expr.strip() expr = expr.replace(' ', '') diff --git a/python_symb/expr.py b/python_symb/expr.py deleted file mode 100644 index 8e00262..0000000 --- a/python_symb/expr.py +++ /dev/null @@ -1,63 +0,0 @@ -from __future__ import annotations -from tree import Tree -from operator_file import Add, Mul -from operator_file import UnaryOperator, BinOperator, Add, Mul, Min -from typing import List -from symbols import Var -from parse import infix_str_to_postfix - - -class Expr(Tree): - def __init__(self, value, children=None): - super().__init__(value, children if children else []) - - @staticmethod - def from_postfix_list(postfix: List): - - def aux(): - first = postfix.pop() - match first: - case int() | Var(): - return Tree(first) - case UnaryOperator(): - return Tree(first, [aux()]) - case BinOperator(): - return Tree(first, [aux(), aux()]) - - return aux() - - @staticmethod - def from_infix_str(expr_str): - expr_rev_polish = infix_str_to_postfix(expr_str) - return Expr.from_postfix_list(expr_rev_polish) - - def bin_op_constructor(self, other, op): - match other: - case Expr(): - return Expr(op, [self, other]) - case Var() | int() | float(): - return Expr(op, [self, Expr(other)]) - case _: - return ValueError(f'Invalid type for operation: {other} : {type(other)}') - - def __add__(self, other): - self.bin_op_constructor(other, Add) - - def __mul__(self, other): - self.bin_op_constructor(other, Mul) - - def __sub__(self, other): - self.bin_op_constructor(other, Min) - - -if __name__ == '__main__': - x, y = Var('x'), Var('y') - - - - - - - - - diff --git a/python_symb/visual.py b/python_symb/tree_visual.py similarity index 95% rename from python_symb/visual.py rename to python_symb/tree_visual.py index 6cd3f4f..859ed45 100644 --- a/python_symb/visual.py +++ b/python_symb/tree_visual.py @@ -1,6 +1,8 @@ import tkinter as tk -from expr import Expr, Var -from parse import update_symbols_dict +from python_symb.Expressions.expr import Expr +from python_symb.MathTypes.symbols import Var +from python_symb.Parsing.parse import update_symbols_dict + class Visual: def __init__(self): @@ -93,7 +95,7 @@ class Visual: return self.tree_canvas.create_oval(x0, y0, x1, y1, fill=color) def draw_tree(self, tree, first_x=400, first_y=50, x_offset=250, y_offset=200, radius=30, fact_mult=0.75): - + print(tree) children = tree.children n = len(children) for i in range(n):