Add files via upload

This commit is contained in:
Crizomb 2023-08-02 00:39:13 +02:00 committed by GitHub
parent a7461088cd
commit 7e1ff3b6b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 565 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

38
python_symb/expr.py Normal file
View file

@ -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)

131
python_symb/fraction.py Normal file
View file

@ -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)

3
python_symb/integers.py Normal file
View file

@ -0,0 +1,3 @@
"""
Python int is already an arbitrary precision integer, so we don't need to implement it.
"""

View file

@ -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)

114
python_symb/parse.py Normal file
View file

@ -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))

31
python_symb/symbols.py Normal file
View file

@ -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)

14
python_symb/tools.py Normal file
View file

@ -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)

80
python_symb/tree.py Normal file
View file

@ -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)

92
python_symb/visual.py Normal file
View file

@ -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()