Add files via upload
This commit is contained in:
parent
a7461088cd
commit
7e1ff3b6b2
16 changed files with 565 additions and 0 deletions
BIN
python_symb/__pycache__/expr.cpython-311.pyc
Normal file
BIN
python_symb/__pycache__/expr.cpython-311.pyc
Normal file
Binary file not shown.
BIN
python_symb/__pycache__/fraction.cpython-311.pyc
Normal file
BIN
python_symb/__pycache__/fraction.cpython-311.pyc
Normal file
Binary file not shown.
BIN
python_symb/__pycache__/operator_file.cpython-311.pyc
Normal file
BIN
python_symb/__pycache__/operator_file.cpython-311.pyc
Normal file
Binary file not shown.
BIN
python_symb/__pycache__/parse.cpython-311.pyc
Normal file
BIN
python_symb/__pycache__/parse.cpython-311.pyc
Normal file
Binary file not shown.
BIN
python_symb/__pycache__/symbols.cpython-311.pyc
Normal file
BIN
python_symb/__pycache__/symbols.cpython-311.pyc
Normal file
Binary file not shown.
BIN
python_symb/__pycache__/tools.cpython-311.pyc
Normal file
BIN
python_symb/__pycache__/tools.cpython-311.pyc
Normal file
Binary file not shown.
BIN
python_symb/__pycache__/tree.cpython-311.pyc
Normal file
BIN
python_symb/__pycache__/tree.cpython-311.pyc
Normal file
Binary file not shown.
38
python_symb/expr.py
Normal file
38
python_symb/expr.py
Normal 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
131
python_symb/fraction.py
Normal 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
3
python_symb/integers.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
"""
|
||||
Python int is already an arbitrary precision integer, so we don't need to implement it.
|
||||
"""
|
62
python_symb/operator_file.py
Normal file
62
python_symb/operator_file.py
Normal 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
114
python_symb/parse.py
Normal 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
31
python_symb/symbols.py
Normal 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
14
python_symb/tools.py
Normal 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
80
python_symb/tree.py
Normal 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
92
python_symb/visual.py
Normal 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()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue