diff --git a/python_symb/__pycache__/expr.cpython-311.pyc b/python_symb/__pycache__/expr.cpython-311.pyc new file mode 100644 index 0000000..a46f047 Binary files /dev/null and b/python_symb/__pycache__/expr.cpython-311.pyc differ diff --git a/python_symb/__pycache__/fraction.cpython-311.pyc b/python_symb/__pycache__/fraction.cpython-311.pyc new file mode 100644 index 0000000..07a9c8a Binary files /dev/null and b/python_symb/__pycache__/fraction.cpython-311.pyc differ diff --git a/python_symb/__pycache__/operator_file.cpython-311.pyc b/python_symb/__pycache__/operator_file.cpython-311.pyc new file mode 100644 index 0000000..59cbcc9 Binary files /dev/null and b/python_symb/__pycache__/operator_file.cpython-311.pyc differ diff --git a/python_symb/__pycache__/parse.cpython-311.pyc b/python_symb/__pycache__/parse.cpython-311.pyc new file mode 100644 index 0000000..bce1ac4 Binary files /dev/null and b/python_symb/__pycache__/parse.cpython-311.pyc differ diff --git a/python_symb/__pycache__/symbols.cpython-311.pyc b/python_symb/__pycache__/symbols.cpython-311.pyc new file mode 100644 index 0000000..60a6d73 Binary files /dev/null and b/python_symb/__pycache__/symbols.cpython-311.pyc differ diff --git a/python_symb/__pycache__/tools.cpython-311.pyc b/python_symb/__pycache__/tools.cpython-311.pyc new file mode 100644 index 0000000..9c4ba3f Binary files /dev/null and b/python_symb/__pycache__/tools.cpython-311.pyc differ diff --git a/python_symb/__pycache__/tree.cpython-311.pyc b/python_symb/__pycache__/tree.cpython-311.pyc new file mode 100644 index 0000000..64d3f43 Binary files /dev/null and b/python_symb/__pycache__/tree.cpython-311.pyc differ diff --git a/python_symb/expr.py b/python_symb/expr.py new file mode 100644 index 0000000..a8b2c4f --- /dev/null +++ b/python_symb/expr.py @@ -0,0 +1,38 @@ +from __future__ import annotations +from tree import Tree +from operator_file import Add, Mul +from operator_file import UnaryOperator, BinOperator +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) + + + + + + diff --git a/python_symb/fraction.py b/python_symb/fraction.py new file mode 100644 index 0000000..8f09354 --- /dev/null +++ b/python_symb/fraction.py @@ -0,0 +1,131 @@ +from __future__ import annotations +from typing import Iterable, Generator +from tools import gcd + + +class Fraction: + """ + Should represent a fraction not a division + """ + __slots__ = ['num', 'den'] + __match_args__ = ("num", "den") + + #todo check if num and den are part of a domain, so a/b as a meaning a gcd work well + #todo implement __iadd__ etc... if performance needed + + def __init__(self, *args): + match args: + case num, den: + self.num = num + self.den = den + case x, : + self.num = x + self.den = 1 + + def __repr__(self): + return f'Fractions({self.num}, {self.den})' + + def simplify_gcd(self): + """Simplify fraction by diving num and den by their gcd + return None""" + match self.num, self.den: + case int(num), int(den): + gcd_ = gcd(num, den) + self.num //= gcd_ + self.den //= gcd_ + # can be completed with others objects that support gcd like polynomials etc... + + def simplify_to_num(self): + """from frac(a, 1) return a.""" + if self.den == 1: + return self.num + def simplify_nested(self, rec=True): + """simplify nested fractions. + Fractions(1, Fractions(1, Fractions(1, Fractions(1, 2)))) -> Fractions(2, 1) + For one simplification step put rec=False + + return None""" + + def aux(fract): + match fract: + case Fraction(Fraction(a, b), Fraction(c, d)): + fract.num = a * d + fract.den = b * c + case Fraction(num, Fraction(a, b)): + fract.num = num * b + fract.den = a + case Fraction(Fraction(a, b), den): + fract.num = a + fract.den = b * den + + if rec: + num, den = self.num, self.den + if isinstance(num, Fraction) or isinstance(den, Fraction): + aux(fract) + + aux(self) + + def simplify_all_(self): + self.simplify_gcd() + self.simplify_nested() + res = self.simplify_to_num() + if res: + return res + + return self + + def __add__(self, other): + match other: + case int(x): + return Fraction(self.num + self.den * x, self.den) + case Fraction(num, den): + result = Fraction(self.num * den + num * self.den, self.den * den) + return result + return ValueError + + def __radd__(self, other): + return other + self + + def __neg__(self): + return Fraction(-self.num, self.den) + + def __mul__(self, other): + match other: + case int(x): + return Fraction(self.num * x, self.den) + case Fraction(num, den): + result = Fraction(self.num * num, self.den * den) + return result + return ValueError + + def __rmul__(self, other): + return self*other + + def __truediv__(self, other): + match other: + case int(x): + return Fraction(self.num, self.den * x) + case Fraction(num, den): + return Fraction(self.num * den, self.den * num) + + def __rtruediv__(self, other): + res = self/other + return Fraction(res.den, res.num) + + +if __name__ == "__main__": + a = Fraction(1, 2) + a += 1 + print(a) + + + + + + + + + + + + diff --git a/python_symb/integers.py b/python_symb/integers.py new file mode 100644 index 0000000..cc56d3b --- /dev/null +++ b/python_symb/integers.py @@ -0,0 +1,3 @@ +""" +Python int is already an arbitrary precision integer, so we don't need to implement it. +""" diff --git a/python_symb/operator_file.py b/python_symb/operator_file.py new file mode 100644 index 0000000..1c935e9 --- /dev/null +++ b/python_symb/operator_file.py @@ -0,0 +1,62 @@ +from __future__ import annotations +from typing import Dict, Callable +from symbols import Symbols + + +class Operator(Symbols): + instances = [] + def __init__(self, name : str, precedence: int, call: Callable): + super().__init__(name) + self.precedence = precedence + self.call = call + Operator.instances.append(self) + + def __repr__(self): + return f'{self.name}' + + +class UnaryOperator(Operator): + instances = [] + + def __init__(self, name: str, precedence: int, call: Callable): + UnaryOperator.instances.append(self) + super().__init__(name, precedence, call) + + def __call__(self, expr): + return self.call(expr) + + +class BinProperties: + + def __init__(self, associativity: bool, commutativity: True, + left_distributivity: Dict[str, bool], right_distributivity: Dict[str, bool]): + + self.associativity = associativity + self.commutativity = commutativity + self.left_distributivity = left_distributivity + self.right_distributivity = right_distributivity + + +class BinOperator(Operator): + instances = [] + + def __init__(self, name: str, precedence: int, properties: BinProperties, call: Callable): + BinOperator.instances.append(self) + super().__init__(name, precedence, call) + self.properties = properties + + def __call__(self, left, right): + return self.call(left, right) + + +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', 0, lambda x: x) + +Min = BinOperator('-', 1, AddProperties, lambda x, y: x - y) +# Pns = UnaryOperator('()', 0, lambda x: x) + diff --git a/python_symb/parse.py b/python_symb/parse.py new file mode 100644 index 0000000..da16714 --- /dev/null +++ b/python_symb/parse.py @@ -0,0 +1,114 @@ +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 + +x, y = Var('x'), Var('y') + +ParenthesisLeft = Symbols('(') +ParenthesisRight = Symbols(')') + +Number = Union[int, float, Fraction] + +name_to_symbol = {sy.name:sy for sy in Symbols.instances} + +print(name_to_symbol) + +""" +example1 = "a + b * c + d" +- +example2 = 2*x+3*y*(4+sin(5)) +- +example3 = "(a + b) * (c + -d))" +should return Tree(Expr('*', [Expr('+', [Expr('a'), Expr('b')]), Expr('+', [Expr('c'), Expr('-', [Expr('d')])])])) +""" + + +def preprocess(expr: str) -> List: + """ + Preprocesses a string expression to a list of symbols and numbers + :param expr: string expression + :return: list of symbols and numbers + """ + return_list = [] + expr = expr.strip() + expr = expr.replace(' ', '') + m = max([len(sy) for sy in name_to_symbol.keys()]) + i = 0 + while i < len(expr): + found = False + for j in range(m, 0, -1): + word = expr[i:i+j] + if word in name_to_symbol or word.isdigit(): + if word in name_to_symbol: + return_list.append(name_to_symbol[word]) + else: + return_list.append(int(word)) + i += j + found = True + break + + if not found: + raise ValueError(f'Invalid expression: {expr} at index {i}\n') + return return_list + + +def return_to_string(expr: List) -> str: + """ + Returns a string expression from a list of symbols and numbers + :param expr: list of symbols and numbers + :return: string expression + """ + return ' '.join([str(sy) for sy in expr]) + + +def infix_to_postfix(expr: List) -> List: + global ParenthesisLeft, ParenthesisRight + """ + Converts an infix string expression (standard) to a postfix expression (reverse polish notation) + :param expr: infix expression + :return: postfix expression + + use shunting yard algorithm + """ + op_stack = [] + postfix = [] + for sy in expr: + match sy: + case int() | float() | Fraction() | Var(): + postfix.append(sy) + case _ if sy == ParenthesisLeft: + op_stack.append(sy) + case _ if sy == ParenthesisRight: + while op_stack[-1] != ParenthesisLeft: + postfix.append(op_stack.pop()) + op_stack.pop() + case UnaryOperator(): + op_stack.append(sy) + case BinOperator(): + while op_stack and op_stack[-1] != ParenthesisLeft and op_stack[-1].precedence >= sy.precedence: + postfix.append(op_stack.pop()) + op_stack.append(sy) + while op_stack: + postfix.append(op_stack.pop()) + return postfix + + +def infix_str_to_postfix(expr): + return infix_to_postfix(preprocess(expr)) + + +if __name__ == "__main__": + + expr = "(x+7)*y+sin(24-2*(1-5))" + prep = preprocess(expr) + print(prep) + post = infix_to_postfix(prep) + print(post) + print(return_to_string(post)) + + + + + diff --git a/python_symb/symbols.py b/python_symb/symbols.py new file mode 100644 index 0000000..f853677 --- /dev/null +++ b/python_symb/symbols.py @@ -0,0 +1,31 @@ +from __future__ import annotations + + +class Symbols: + """ + All maths things (other than number) that will be parsed need to be of "Symbols" class + """ + instances = [] + + def __init__(self, name): + self.name = name + Symbols.instances.append(self) + + def __repr__(self): + return self.name + + def __str__(self): + return self.name + + +class Var(Symbols): + """ + variable, like 'x' in x+2 + """ + instances = [] + + def __init__(self, name): + super().__init__(name) + print(self.__class__) + self.__class__.instances.append(self) + diff --git a/python_symb/tools.py b/python_symb/tools.py new file mode 100644 index 0000000..3d3b00a --- /dev/null +++ b/python_symb/tools.py @@ -0,0 +1,14 @@ +from __future__ import annotations +from typing import * + + +def gcd(a, b): + + if b > a: + return gcd(b, a) + + if b == 0: + return a + + return gcd(b, a % b) + diff --git a/python_symb/tree.py b/python_symb/tree.py new file mode 100644 index 0000000..ec1a252 --- /dev/null +++ b/python_symb/tree.py @@ -0,0 +1,80 @@ +from __future__ import annotations +from typing import Iterable, Generator +from collections import deque + + +class Tree: + """ + 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... + + 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. + for instance to write "a ? b : c" you need to write Tree("?", [Tree("a"), Tree("b"), Tree("c")]) + and set the depth_first_order of the "?" node to 1. + + children : the children of the node. Can be empty. + """ + __slots__ = ['value', 'children', 'depth_first_order'] + + def __init__(self, value, children: Iterable[Tree] = None, depth_first_order: int = 0): + self.value = value + self.depth_first_order = depth_first_order + 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})' + + def height(self) -> int: + return 1 + max((child.height() for child in self.children), default=0) + + def size(self) -> int: + return 1 + sum(child.size() for child in self.children) + + def breadth_first(self) -> Generator[Tree]: + + queue = deque([self]) + + while queue: + poped = queue.popleft() + for child in poped.children: + queue.append(child) + + yield poped + + def depth_first_default(self) -> Generator[Tree]: + + def aux(tree): + n = len(tree.children) + if not tree.children: + yield tree + + for i, child in enumerate(tree.children): + if i == tree.depth_first_order: + yield tree + + yield from aux(child) + + if tree.depth_first_order == -1: + yield tree + + yield from aux(self) + + def depth_first_pre_order(self) -> Generator[Tree]: + + def aux(tree): + yield tree + for child in tree.children: + yield from aux(child) + + yield from aux(self) + + def depth_first_post_order(self) -> Generator[Tree]: + + def aux(tree): + for child in tree.children: + yield from aux(child) + yield tree + + yield from aux(self) \ No newline at end of file diff --git a/python_symb/visual.py b/python_symb/visual.py new file mode 100644 index 0000000..758d02a --- /dev/null +++ b/python_symb/visual.py @@ -0,0 +1,92 @@ +import tkinter as tk +from expr import Expr + +class Visual: + def __init__(self): + self.root = tk.Tk() + self.root.title("Visual") + self.root.geometry("800x800") + + # Add a canvas on the left side of the window + self.tree_canvas = tk.Canvas(self.root, width=500, height=500, bg="white") + self.tree_canvas.pack(side=tk.LEFT, expand=False) + + # Create right frame + self.right_frame = tk.Frame(self.root, width=300, height=500, background="grey") + + + # Add a label with the text "Input" on top of the right frame, should not take all the space + self.input_label = tk.Label(self.right_frame, text="Input (infix string):") + + + # Add a entry below the label with default text "(5+2)*3" + self.input_entry = tk.Entry(self.right_frame, width=50) + self.input_entry.insert(0, "(5+2)*3") + + + + # Add a button below the entry with the text "To Tree" + self.input_button = tk.Button(self.right_frame, text="To Tree", command=self.show_tree) + + # Pack the widgets, make sure they are not expanding, and all are fixed size + + + self.input_label.pack() + self.input_entry.pack() + self.input_button.pack() + self.right_frame.pack(side=tk.LEFT, expand=False) + + + + + + + self.root.mainloop() + + def show_tree(self): + # Get the text from the entry + text = self.input_entry.get() + + # Clear the canvas + self.tree_canvas.delete("all") + tree = Expr.from_infix_str(text) + + # Draw the tree + self.draw_tree(tree) + + def create_circle(self, x, y, r, color): # center coordinates, radius + x0 = x - r + y0 = y - r + x1 = x + r + y1 = y + r + return self.tree_canvas.create_oval(x0, y0, x1, y1, fill=color) + + def draw_tree(self, tree, first_x=250, first_y=50, x_offset=100, y_offset=100): + + children = tree.children + n = len(children) + for i in range(n): + child = children[i] + x = first_x + (i - (n - 1) / 2) * x_offset + y = first_y + y_offset + + #Link the node to the parent + self.tree_canvas.create_line(first_x, first_y, x, y) + # Draw the node + self.draw_tree(child, int(x), y, x_offset, y_offset) + + # Draw the root node + self.create_circle(first_x, first_y, 20, "red") + self.tree_canvas.create_text(first_x, first_y, text=str(tree.value)) + +if __name__ == "__main__": + Visual() + + + + + + + + +