diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index acd9288..99b3fa4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -20,7 +20,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v2
with:
- python-version: '3.10' # Specify the version of Python you want to use
+ python-version: '3.10'
- name: Install dependencies
run: |
diff --git a/.idea/etu_algo_labs.iml b/.idea/etu_algo_labs.iml
index d0876a7..c96f2f7 100644
--- a/.idea/etu_algo_labs.iml
+++ b/.idea/etu_algo_labs.iml
@@ -1,8 +1,10 @@
-
-
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 8b36567..616b17c 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,5 +3,5 @@
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 40cfd0b..7b9fee0 100644
--- a/README.md
+++ b/README.md
@@ -37,8 +37,9 @@ For a visual demonstration of the algorithm, check out the following video: [Shu
The core implementation is contained within the `Solution` class. Here’s a brief overview of the code:
```python
-from algo.Queue import Queue
-from algo.Stack import Stack
+from src.algo import Queue
+from src.algo import Stack
+
class Solution:
def __init__(self):
@@ -61,7 +62,7 @@ class Solution:
self.__stack.pop()
else:
while self.__stack and self.get_priority(
- self.__stack.top.data
+ self.__stack.top.data
) >= self.get_priority(elem):
self.__queue.push(self.__stack.pop())
self.__stack.push(elem)
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..cb5a1dc
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,15 @@
+[tool.black]
+line-length = 79
+target-version = ['py37']
+include = '\.pyi?$'
+exclude = '''
+/(
+ .git
+ | __pycache__
+ | docs/source/conf.py
+ | old
+ | build
+ | dist
+ | .venv
+)/
+'''
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..5b18616
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,8 @@
+from setuptools import find_packages, setup
+
+setup(
+ name="algo_labs",
+ version="0.1.0",
+ packages=find_packages(where="src"),
+ package_dir={"": "src"},
+)
diff --git a/algo/__init__.py b/src/ShuntingYard/__init__.py
similarity index 100%
rename from algo/__init__.py
rename to src/ShuntingYard/__init__.py
diff --git a/Shunting Yard/solution.py b/src/ShuntingYard/solution.py
similarity index 87%
rename from Shunting Yard/solution.py
rename to src/ShuntingYard/solution.py
index facb0f4..1e70961 100644
--- a/Shunting Yard/solution.py
+++ b/src/ShuntingYard/solution.py
@@ -1,6 +1,6 @@
-from algo.LineCheck import is_valid
-from algo.Queue import Queue
-from algo.Stack import Stack
+from src.algo.line_check import validate_brackets
+from src.algo.queue import Queue
+from src.algo.stack import Stack
class Solution:
@@ -13,7 +13,7 @@ def get_priority(elem):
return {"+": 0, "-": 0, "*": 1, "/": 1, "^": 2}.get(elem, -1)
def infix_to_postfix(self, line: str):
- assert is_valid(line)
+ assert validate_brackets(line)
for elem in line.split():
if elem.isnumeric():
self.__queue.push(elem)
diff --git a/src/algo/__init__.py b/src/algo/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/algo/LineCheck.py b/src/algo/line_check.py
similarity index 84%
rename from algo/LineCheck.py
rename to src/algo/line_check.py
index 1866b30..ed6eb67 100644
--- a/algo/LineCheck.py
+++ b/src/algo/line_check.py
@@ -1,7 +1,7 @@
-from .Stack import Stack
+from src.algo.stack import Stack
-def is_valid(bracket_sequence):
+def validate_brackets(bracket_sequence):
stack = Stack()
brackets_dict = {
"[": "]",
diff --git a/algo/Queue.py b/src/algo/queue.py
similarity index 100%
rename from algo/Queue.py
rename to src/algo/queue.py
diff --git a/algo/Stack.py b/src/algo/stack.py
similarity index 100%
rename from algo/Stack.py
rename to src/algo/stack.py
diff --git a/src/timsort/__init__.py b/src/timsort/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/timsort/solution.py b/src/timsort/solution.py
similarity index 82%
rename from timsort/solution.py
rename to src/timsort/solution.py
index ffec6a6..b0f8788 100644
--- a/timsort/solution.py
+++ b/src/timsort/solution.py
@@ -1,7 +1,7 @@
-from algo.Stack import Stack
+from src.algo.stack import Stack
-def calculate_minrun(n):
+def calculate_min_run(n):
count = 0
while n >= 64:
count |= n & 1
@@ -9,7 +9,7 @@ def calculate_minrun(n):
return n + count
-def insertion_sort(arr, left, right):
+def insertion_sort(arr, left: list, right):
for i in range(left + 1, right + 1):
key_item = arr[i]
j = i - 1
@@ -50,15 +50,16 @@ def merge(arr, start, mid, end):
if count >= gallop_trigger:
if left[i] <= right[j]:
- idx = binary_search(right, left[i], j)
- arr[start + k : start + k + idx - j] = right[j:idx]
- k += idx - j
- j = idx
+ index = binary_search(right, left[i], j)
+ arr[start + k : start + k + index - j] = right[j:index]
+ k += index - j
+ j = index
else:
- idx = binary_search(left, right[j], i)
- arr[start + k : start + k + idx - i] = left[i:idx]
- k += idx - i
- i = idx
+ index = binary_search(left, right[j], i)
+ arr[start + k : start + k + index - i] = left[i:index]
+ k += index - i
+ i = index
+
while i < len(left):
arr[start + k] = left[i]
i += 1
@@ -99,7 +100,7 @@ def find_runs(arr, minrun):
def timsort(arr):
n = len(arr)
- minrun = calculate_minrun(n)
+ minrun = calculate_min_run(n)
runs = find_runs(arr, minrun)
stack = Stack()
diff --git a/src/tree/__init__.py b/src/tree/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/tree/binary_tree.py b/src/tree/binary_tree.py
new file mode 100644
index 0000000..f028169
--- /dev/null
+++ b/src/tree/binary_tree.py
@@ -0,0 +1,38 @@
+from .traversal import TraversalStrategy
+
+
+class BinaryTree:
+ def __init__(self, root=None):
+ self.root = root
+
+ def traverse(self, strategy: TraversalStrategy):
+ return strategy.traverse(self.root)
+
+ def delete(self, value):
+ self.root = self._delete_rec(self.root, value)
+
+ def _delete_rec(self, node, value):
+ if node is None:
+ return None
+ if value < node.get_value():
+ node.set_left(self._delete_rec(node.get_left(), value))
+ elif value > node.get_value():
+ node.set_right(self._delete_rec(node.get_right(), value))
+ else:
+ if node.get_left() is None:
+ return node.get_right()
+ elif node.get_right() is None:
+ return node.get_left()
+ temp = self._min_value_node(node.get_right())
+ node.set_value(temp.get_value())
+ node.set_right(
+ self._delete_rec(node.get_right(), temp.get_value())
+ )
+ return node
+
+ @staticmethod
+ def _min_value_node(node):
+ current = node
+ while current.get_left() is not None:
+ current = current.get_left()
+ return current
diff --git a/src/tree/main.py b/src/tree/main.py
new file mode 100644
index 0000000..94d4daf
--- /dev/null
+++ b/src/tree/main.py
@@ -0,0 +1,63 @@
+from src.tree.binary_tree import BinaryTree
+from src.tree.parser import TreeParser
+from src.tree.red_black_tree import RedBlackTree
+from src.tree.traversal import (
+ BreadthFirstTraversal,
+ InOrderTraversal,
+ PostOrderTraversal,
+ PreOrderTraversal,
+)
+
+
+def read_tree_from_file(file_name):
+ with open(file_name, "r") as file:
+ tree_string = file.read().strip()
+ parser = TreeParser(tree_string)
+ root = parser.parse()
+ return root
+
+
+def build_red_black_tree(bin_root):
+ tree = RedBlackTree()
+
+ def insert_node(node):
+ if node:
+ try:
+ value = int(node.get_value())
+ except ValueError:
+ value = node.get_value()
+ tree.insert(value)
+ insert_node(node.get_left())
+ insert_node(node.get_right())
+
+ insert_node(bin_root)
+ return tree
+
+
+def demonstrate_traversals(tree):
+ strategies = {
+ "Обход в ширину": BreadthFirstTraversal(),
+ "Обход в глубину (прямой)": PreOrderTraversal(),
+ "Обход в глубину (симметричный)": InOrderTraversal(),
+ "Обход в глубину (обратный)": PostOrderTraversal(),
+ }
+
+ for name, strategy in strategies.items():
+ result = tree.traverse(strategy)
+ print(f"{name}: {' '.join(map(str, result))}")
+
+
+if __name__ == "__main__":
+ filename = "tree.txt"
+ binary_root = read_tree_from_file(filename)
+ if not binary_root:
+ print("Не удалось построить дерево.")
+ exit(1)
+ binary_tree = BinaryTree(binary_root)
+ print("Построено двоичное дерево.")
+ demonstrate_traversals(binary_tree)
+
+ print("\nПостроение красно-черного дерева из двоичного дерева...")
+ rb_tree = build_red_black_tree(binary_root)
+ print("Вывод узлов красно-черного дерева:")
+ demonstrate_traversals(rb_tree)
diff --git a/src/tree/nodes.py b/src/tree/nodes.py
new file mode 100644
index 0000000..017a299
--- /dev/null
+++ b/src/tree/nodes.py
@@ -0,0 +1,52 @@
+from abc import ABC, abstractmethod
+
+
+class TreeNode(ABC):
+ @abstractmethod
+ def get_value(self):
+ pass
+
+ @abstractmethod
+ def set_value(self, value):
+ pass
+
+ @abstractmethod
+ def get_left(self):
+ pass
+
+ @abstractmethod
+ def get_right(self):
+ pass
+
+ @abstractmethod
+ def set_left(self, node):
+ pass
+
+ @abstractmethod
+ def set_right(self, node):
+ pass
+
+
+class BinaryTreeNode(TreeNode):
+ def __init__(self, value):
+ self._value = value
+ self._left = None
+ self._right = None
+
+ def get_value(self):
+ return self._value
+
+ def set_value(self, value):
+ self._value = value
+
+ def get_left(self):
+ return self._left
+
+ def get_right(self):
+ return self._right
+
+ def set_left(self, node):
+ self._left = node
+
+ def set_right(self, node):
+ self._right = node
diff --git a/src/tree/parser.py b/src/tree/parser.py
new file mode 100644
index 0000000..12f34ab
--- /dev/null
+++ b/src/tree/parser.py
@@ -0,0 +1,34 @@
+from .nodes import BinaryTreeNode
+
+
+class TreeParser:
+ def __init__(self, line):
+ self.tokens = line.replace("(", " ( ").replace(")", " ) ").split()
+ self.index = 0
+
+ def parse(self):
+ if self.index >= len(self.tokens):
+ return None
+
+ token = self.tokens[self.index]
+ if token == "(":
+ self.index += 1
+ if self.index >= len(self.tokens):
+ raise Exception("Ожидалось значение после '('")
+ value = self.tokens[self.index]
+ self.index += 1
+ node = BinaryTreeNode(value)
+ node.set_left(self.parse())
+ node.set_right(self.parse())
+ if (
+ self.index >= len(self.tokens)
+ or self.tokens[self.index] != ")"
+ ):
+ raise Exception("Ожидалась ')'")
+ self.index += 1
+ return node
+ elif token == ")":
+ return None
+ else:
+ self.index += 1
+ return BinaryTreeNode(token)
diff --git a/src/tree/red_black_tree.py b/src/tree/red_black_tree.py
new file mode 100644
index 0000000..1cd0695
--- /dev/null
+++ b/src/tree/red_black_tree.py
@@ -0,0 +1,228 @@
+from .nodes import BinaryTreeNode
+from .traversal import TraversalStrategy
+
+
+class RBTreeNode(BinaryTreeNode):
+ def __init__(self, value, color="RED"):
+ super().__init__(value)
+ self.color = color
+ self.left = None
+ self.right = None
+ self.parent = None
+
+ def get_left(self):
+ return self.left
+
+ def get_right(self):
+ return self.right
+
+ def set_left(self, node):
+ self.left = node
+
+ def set_right(self, node):
+ self.right = node
+
+
+class RedBlackTree:
+ def __init__(self):
+ self.nil = RBTreeNode(None, color="BLACK")
+ self.nil.left = self.nil.right = self.nil
+ self.nil.parent = None
+ self.root = self.nil
+
+ def insert(self, value):
+ new_node = RBTreeNode(value)
+ new_node.left = self.nil
+ new_node.right = self.nil
+ new_node.color = "RED"
+ parent = None
+ current = self.root
+
+ while current != self.nil:
+ parent = current
+ if new_node.get_value() < current.get_value():
+ current = current.get_left()
+ else:
+ current = current.get_right()
+
+ new_node.parent = parent
+
+ if parent is None:
+ self.root = new_node
+ elif new_node.get_value() < parent.get_value():
+ parent.set_left(new_node)
+ else:
+ parent.set_right(new_node)
+
+ self.insert_fixup(new_node)
+
+ def insert_fixup(self, node):
+ while node.parent and node.parent.color == "RED":
+ if node.parent == node.parent.parent.get_left():
+ uncle = node.parent.parent.get_right()
+ if uncle.color == "RED":
+ node.parent.color = "BLACK"
+ uncle.color = "BLACK"
+ node.parent.parent.color = "RED"
+ node = node.parent.parent
+ else:
+ if node == node.parent.get_right():
+ node = node.parent
+ self.left_rotate(node)
+ node.parent.color = "BLACK"
+ node.parent.parent.color = "RED"
+ self.right_rotate(node.parent.parent)
+ else:
+ uncle = node.parent.parent.get_left()
+ if uncle.color == "RED":
+ node.parent.color = "BLACK"
+ uncle.color = "BLACK"
+ node.parent.parent.color = "RED"
+ node = node.parent.parent
+ else:
+ if node == node.parent.get_left():
+ node = node.parent
+ self.right_rotate(node)
+ node.parent.color = "BLACK"
+ node.parent.parent.color = "RED"
+ self.left_rotate(node.parent.parent)
+ self.root.color = "BLACK"
+
+ def left_rotate(self, x):
+ y = x.get_right()
+ x.set_right(y.get_left())
+ if y.get_left() != self.nil:
+ y.get_left().parent = x
+ y.parent = x.parent
+ if x.parent is None:
+ self.root = y
+ elif x == x.parent.get_left():
+ x.parent.set_left(y)
+ else:
+ x.parent.set_right(y)
+ y.set_left(x)
+ x.parent = y
+
+ def right_rotate(self, y):
+ x = y.get_left()
+ y.set_left(x.get_right())
+ if x.get_right() != self.nil:
+ x.get_right().parent = y
+ x.parent = y.parent
+ if y.parent is None:
+ self.root = x
+ elif y == y.parent.get_right():
+ y.parent.set_right(x)
+ else:
+ y.parent.set_left(x)
+ x.set_right(y)
+ y.parent = x
+
+ def search(self, value):
+ current = self.root
+ while current != self.nil and current.get_value() != value:
+ if value < current.get_value():
+ current = current.get_left()
+ else:
+ current = current.get_right()
+ return current
+
+ def minimum(self, node):
+ while node.get_left() != self.nil:
+ node = node.get_left()
+ return node
+
+ def transplant(self, u, v):
+ if u.parent is None:
+ self.root = v
+ elif u == u.parent.get_left():
+ u.parent.set_left(v)
+ else:
+ u.parent.set_right(v)
+ v.parent = u.parent
+
+ def delete(self, value):
+ node = self.search(value)
+ if node == self.nil:
+ return False # Узел не найден
+
+ y = node
+ y_original_color = y.color
+ if node.get_left() == self.nil:
+ x = node.get_right()
+ self.transplant(node, node.get_right())
+ elif node.get_right() == self.nil:
+ x = node.get_left()
+ self.transplant(node, node.get_left())
+ else:
+ y = self.minimum(node.get_right())
+ y_original_color = y.color
+ x = y.get_right()
+ if y.parent == node:
+ x.parent = y
+ else:
+ self.transplant(y, y.get_right())
+ y.set_right(node.get_right())
+ y.get_right().parent = y
+ self.transplant(node, y)
+ y.set_left(node.get_left())
+ y.get_left().parent = y
+ y.color = node.color
+ if y_original_color == "BLACK":
+ self.delete_fixup(x)
+ return True
+
+ def delete_fixup(self, x):
+ while x != self.root and x.color == "BLACK":
+ if x == x.parent.get_left():
+ w = x.parent.get_right()
+ if w.color == "RED":
+ w.color = "BLACK"
+ x.parent.color = "RED"
+ self.left_rotate(x.parent)
+ w = x.parent.get_right()
+ if (
+ w.get_left().color == "BLACK"
+ and w.get_right().color == "BLACK"
+ ):
+ w.color = "RED"
+ x = x.parent
+ else:
+ if w.get_right().color == "BLACK":
+ w.get_left().color = "BLACK"
+ w.color = "RED"
+ self.right_rotate(w)
+ w = x.parent.get_right()
+ w.color = x.parent.color
+ x.parent.color = "BLACK"
+ w.get_right().color = "BLACK"
+ self.left_rotate(x.parent)
+ x = self.root
+ else:
+ w = x.parent.get_left()
+ if w.color == "RED":
+ w.color = "BLACK"
+ x.parent.color = "RED"
+ self.right_rotate(x.parent)
+ w = x.parent.get_left()
+ if (
+ w.get_right().color == "BLACK"
+ and w.get_left().color == "BLACK"
+ ):
+ w.color = "RED"
+ x = x.parent
+ else:
+ if w.get_left().color == "BLACK":
+ w.get_right().color = "BLACK"
+ w.color = "RED"
+ self.left_rotate(w)
+ w = x.parent.get_left()
+ w.color = x.parent.color
+ x.parent.color = "BLACK"
+ w.get_left().color = "BLACK"
+ self.right_rotate(x.parent)
+ x = self.root
+ x.color = "BLACK"
+
+ def traverse(self, strategy: TraversalStrategy):
+ return strategy.traverse(self.root, self.nil)
diff --git a/src/tree/traversal.py b/src/tree/traversal.py
new file mode 100644
index 0000000..29ebd78
--- /dev/null
+++ b/src/tree/traversal.py
@@ -0,0 +1,82 @@
+from abc import ABC, abstractmethod
+
+from src.algo.queue import Queue
+
+
+class TraversalStrategy(ABC):
+ @abstractmethod
+ def traverse(self, root, nil=None):
+ pass
+
+
+class PreOrderTraversal(TraversalStrategy):
+ def traverse(self, root, nil=None):
+ result = []
+ stack = []
+ if root and root != nil:
+ stack.append(root)
+ while stack:
+ node = stack.pop()
+ result.append(node.get_value())
+ if node.get_right() and node.get_right() != nil:
+ stack.append(node.get_right())
+ if node.get_left() and node.get_left() != nil:
+ stack.append(node.get_left())
+ return result
+
+
+class InOrderTraversal(TraversalStrategy):
+ def traverse(self, root, nil=None):
+ result = []
+ stack = []
+ current = root
+ while stack or (current and current != nil):
+ if current and current != nil:
+ stack.append(current)
+ current = current.get_left()
+ else:
+ current = stack.pop()
+ result.append(current.get_value())
+ current = current.get_right()
+ return result
+
+
+class PostOrderTraversal(TraversalStrategy):
+ def traverse(self, root, nil=None):
+ result = []
+ stack = []
+ last_node_visited = None
+ current = root
+ while stack or (current and current != nil):
+ if current and current != nil:
+ stack.append(current)
+ current = current.get_left()
+ else:
+ peek_node = stack[-1]
+ if (
+ peek_node.get_right()
+ and last_node_visited != peek_node.get_right()
+ and peek_node.get_right() != nil
+ ):
+ current = peek_node.get_right()
+ else:
+ result.append(peek_node.get_value())
+ last_node_visited = stack.pop()
+ current = None
+ return result
+
+
+class BreadthFirstTraversal(TraversalStrategy):
+ def traverse(self, root, nil=None):
+ result = []
+ queue = Queue()
+ if root and root != nil:
+ queue.push(root)
+ while queue:
+ node = queue.pop()
+ result.append(node.get_value())
+ if node.get_left() and node.get_left() != nil:
+ queue.push(node.get_left())
+ if node.get_right() and node.get_right() != nil:
+ queue.push(node.get_right())
+ return result
diff --git a/src/tree/tree.txt b/src/tree/tree.txt
new file mode 100644
index 0000000..494bbd7
--- /dev/null
+++ b/src/tree/tree.txt
@@ -0,0 +1 @@
+(8 (9 (5)) (1))
\ No newline at end of file
diff --git a/algo/tests.py b/test/test_algo.py
similarity index 90%
rename from algo/tests.py
rename to test/test_algo.py
index a63f9d7..e1b5f69 100644
--- a/algo/tests.py
+++ b/test/test_algo.py
@@ -1,9 +1,7 @@
import unittest
-from .Queue import Queue
-from .Stack import Stack
-
-# from .LineCheck import is_valid
+from src.algo.queue import Queue
+from src.algo.stack import Stack
class TestQueue(unittest.TestCase):
@@ -52,7 +50,3 @@ def test_pop_empty(self):
def test_len_empty(self):
self.setup()
self.assertEqual(len(self.__stack), 0)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/test/test_binary_tree.py b/test/test_binary_tree.py
new file mode 100644
index 0000000..f1ddbd1
--- /dev/null
+++ b/test/test_binary_tree.py
@@ -0,0 +1,44 @@
+import unittest
+
+from src.tree.binary_tree import BinaryTree
+from src.tree.nodes import BinaryTreeNode
+from src.tree.traversal import (
+ BreadthFirstTraversal,
+ InOrderTraversal,
+ PostOrderTraversal,
+ PreOrderTraversal,
+)
+
+
+class TestBinaryTree(unittest.TestCase):
+ def setUp(self):
+ # 8
+ # / \
+ # 5 9
+ self.root = BinaryTreeNode(8)
+ self.root.set_left(BinaryTreeNode(5))
+ self.root.set_right(BinaryTreeNode(9))
+ self.tree = BinaryTree(self.root)
+
+ def test_traversals(self):
+ strategies = {
+ "preorder": (PreOrderTraversal(), [8, 5, 9]),
+ "inorder": (InOrderTraversal(), [5, 8, 9]),
+ "postorder": (PostOrderTraversal(), [5, 9, 8]),
+ "breadth_first": (BreadthFirstTraversal(), [8, 5, 9]),
+ }
+ for name, (strategy, expected) in strategies.items():
+ result = self.tree.traverse(strategy)
+ self.assertEqual(result, expected)
+
+ def test_delete_nonexistent(self):
+ initial_size = len(self.tree.traverse(InOrderTraversal()))
+ self.tree.delete(100)
+ new_size = len(self.tree.traverse(InOrderTraversal()))
+ self.assertEqual(initial_size, new_size)
+
+ def test_delete_existing(self):
+ self.tree.delete(9)
+ expected = [5, 8]
+ result = self.tree.traverse(InOrderTraversal())
+ self.assertEqual(result, expected)
diff --git a/test/test_parser.py b/test/test_parser.py
new file mode 100644
index 0000000..d48111c
--- /dev/null
+++ b/test/test_parser.py
@@ -0,0 +1,43 @@
+import unittest
+
+from src.tree.parser import TreeParser
+
+
+class TestTreeParser(unittest.TestCase):
+ def test_correct_parsing(self):
+ notation = "(8 (9 (5)) (1))"
+ parser = TreeParser(notation)
+ root = parser.parse()
+ self.assertEqual(root.get_value(), "8")
+ self.assertEqual(root.get_left().get_value(), "9")
+ self.assertEqual(root.get_right().get_value(), "1")
+ self.assertEqual(root.get_left().get_left().get_value(), "5")
+
+ def test_incorrect_parsing(self):
+ notation = "(8 (9 (5) (1)"
+ parser = TreeParser(notation)
+ with self.assertRaises(Exception):
+ parser.parse()
+
+ notation = "(8 (9 (5)) (1) extra)"
+ parser = TreeParser(notation)
+ with self.assertRaises(Exception):
+ parser.parse()
+
+ def test_empty_input(self):
+ notation = ""
+ parser = TreeParser(notation)
+ root = parser.parse()
+ self.assertIsNone(root)
+
+ def test_complex_tree_parsing(self):
+ notation = "(1 (2 (4) (5)) (3 (6) (7)))"
+ parser = TreeParser(notation)
+ root = parser.parse()
+ self.assertEqual(root.get_value(), "1")
+ self.assertEqual(root.get_left().get_value(), "2")
+ self.assertEqual(root.get_right().get_value(), "3")
+ self.assertEqual(root.get_left().get_left().get_value(), "4")
+ self.assertEqual(root.get_left().get_right().get_value(), "5")
+ self.assertEqual(root.get_right().get_left().get_value(), "6")
+ self.assertEqual(root.get_right().get_right().get_value(), "7")
diff --git a/test/test_red_black_tree.py b/test/test_red_black_tree.py
new file mode 100644
index 0000000..c49ba35
--- /dev/null
+++ b/test/test_red_black_tree.py
@@ -0,0 +1,207 @@
+import unittest
+
+from src.tree.red_black_tree import RedBlackTree
+from src.tree.traversal import (
+ BreadthFirstTraversal,
+ InOrderTraversal,
+ PostOrderTraversal,
+ PreOrderTraversal,
+)
+
+
+class TestRedBlackTree(unittest.TestCase):
+ def setUp(self):
+ self.tree = RedBlackTree()
+ self.values = [20, 15, 25, 10, 18, 22, 30, 5, 12, 17, 19]
+ for val in self.values:
+ self.tree.insert(val)
+
+ @staticmethod
+ def is_valid_red_black_tree(tree):
+ """
+ Checks whether the given red-black tree satisfies all red-black tree properties:
+
+ 1. Every node is either red or black.
+ 2. The root of the tree is black.
+ 3. All leaves (NIL nodes) are black.
+ 4. If a node is red, both its children must be black
+ (i.e., no two consecutive red nodes are allowed).
+ 5. For each node, all paths from that node to its descendant leaves
+ must contain the same number of black nodes (black height).
+
+ Returns:
+ bool: True if the tree satisfies all properties, False otherwise.
+ """
+
+ def check_properties(node):
+ if node == tree.nil:
+ return 1
+
+ if node.color not in ("RED", "BLACK"):
+ return -1
+
+ if node.color == "RED":
+ if (
+ node.get_left().color != "BLACK"
+ or node.get_right().color != "BLACK"
+ ):
+ return -1
+
+ left_black_height = check_properties(node.get_left())
+ right_black_height = check_properties(node.get_right())
+ if left_black_height == -1 or right_black_height == -1:
+ return -1
+
+ if left_black_height != right_black_height:
+ return -1
+
+ return left_black_height + (1 if node.color == "BLACK" else 0)
+
+ if tree.root.color != "BLACK":
+ return False
+
+ result = check_properties(tree.root)
+ return result != -1
+
+ def test_insert(self):
+ traversal = InOrderTraversal()
+ result = self.tree.traverse(traversal)
+ expected = sorted(self.values)
+ self.assertEqual(result, expected)
+ self.assertTrue(self.is_valid_red_black_tree(self.tree))
+
+ def test_insert_duplicate(self):
+ initial_size = len(self.tree.traverse(InOrderTraversal()))
+ self.tree.insert(20)
+ new_size = len(self.tree.traverse(InOrderTraversal()))
+ self.assertEqual(initial_size + 1, new_size)
+ self.assertTrue(self.is_valid_red_black_tree(self.tree))
+
+ def test_search(self):
+ for val in self.values:
+ node = self.tree.search(val)
+ self.assertIsNotNone(node)
+ self.assertEqual(node.get_value(), val)
+ self.assertEqual(self.tree.search(100), self.tree.nil)
+
+ def test_delete(self):
+ self.tree.delete(15)
+ self.tree.delete(22)
+ traversal = InOrderTraversal()
+ result = self.tree.traverse(traversal)
+ expected = sorted([val for val in self.values if val not in [15, 22]])
+ self.assertEqual(result, expected)
+ self.assertTrue(self.is_valid_red_black_tree(self.tree))
+
+ def test_delete_nonexistent(self):
+ result = self.tree.delete(100)
+ self.assertFalse(result)
+
+ def test_traversals(self):
+ strategies = {
+ "preorder": PreOrderTraversal(),
+ "inorder": InOrderTraversal(),
+ "postorder": PostOrderTraversal(),
+ "breadth_first": BreadthFirstTraversal(),
+ }
+ for name, strategy in strategies.items():
+ result = self.tree.traverse(strategy)
+ self.assertIsInstance(result, list)
+ self.assertEqual(len(result), len(self.values))
+
+ def test_balance(self):
+ self.assertTrue(self.is_valid_red_black_tree(self.tree))
+
+ def test_insert_case1(self):
+ tree = RedBlackTree()
+ tree.insert(10)
+ tree.insert(5)
+ tree.insert(15)
+ tree.insert(1)
+ tree.insert(7)
+ tree.insert(13)
+ tree.insert(17)
+ tree.insert(6)
+ self.assertTrue(self.is_valid_red_black_tree(tree))
+
+ def test_insert_case2_case3(self):
+ tree = RedBlackTree()
+ tree.insert(10)
+ tree.insert(5)
+ tree.insert(15)
+ tree.insert(1)
+ tree.insert(7)
+ tree.insert(6)
+ self.assertTrue(self.is_valid_red_black_tree(tree))
+
+ tree = RedBlackTree()
+ tree.insert(10)
+ tree.insert(5)
+ tree.insert(15)
+ tree.insert(12)
+ tree.insert(17)
+ tree.insert(19)
+ self.assertTrue(self.is_valid_red_black_tree(tree))
+
+ def test_delete_cases(self):
+ tree = RedBlackTree()
+ values = [20, 15, 25, 10, 18, 22, 30, 5, 12, 17, 19]
+ for val in values:
+ tree.insert(val)
+
+ tree.delete(5)
+ self.assertTrue(self.is_valid_red_black_tree(tree))
+
+ tree.delete(22)
+ self.assertTrue(self.is_valid_red_black_tree(tree))
+
+ tree.delete(15)
+ self.assertTrue(self.is_valid_red_black_tree(tree))
+
+ tree.delete(20)
+ self.assertTrue(self.is_valid_red_black_tree(tree))
+
+ def test_delete_case2(self):
+ tree = RedBlackTree()
+ tree.insert(1)
+ tree.insert(2)
+ tree.insert(3)
+ tree.delete(1)
+ self.assertTrue(self.is_valid_red_black_tree(tree))
+
+ def test_delete_case3(self):
+ tree = RedBlackTree()
+ tree.insert(3)
+ tree.insert(2)
+ tree.insert(4)
+ tree.insert(1)
+ tree.delete(4)
+ self.assertTrue(self.is_valid_red_black_tree(tree))
+
+ def test_delete_case4(self):
+ tree = RedBlackTree()
+ tree.insert(3)
+ tree.insert(1)
+ tree.insert(5)
+ tree.insert(0)
+ tree.insert(2)
+ tree.insert(4)
+ tree.insert(6)
+ tree.delete(0)
+ tree.delete(2)
+ self.assertTrue(self.is_valid_red_black_tree(tree))
+
+ def test_delete_case5(self):
+ tree = RedBlackTree()
+ tree.insert(11)
+ tree.insert(2)
+ tree.insert(14)
+ tree.insert(1)
+ tree.insert(7)
+ tree.insert(15)
+ tree.insert(5)
+ tree.insert(8)
+ tree.insert(4)
+ tree.delete(1)
+ tree.delete(2)
+ self.assertTrue(self.is_valid_red_black_tree(tree))
diff --git a/Shunting Yard/tests.py b/test/test_shunting_yard.py
similarity index 81%
rename from Shunting Yard/tests.py
rename to test/test_shunting_yard.py
index c7fb64d..b05806d 100644
--- a/Shunting Yard/tests.py
+++ b/test/test_shunting_yard.py
@@ -1,6 +1,6 @@
import unittest
-from solution import Solution
+from src.ShuntingYard.solution import Solution
class MyTestCase(unittest.TestCase):
@@ -15,7 +15,21 @@ def test_algo(self):
("2 * ( 3 + ( 4 * 5 ) )", ["2", "3", "4", "5", "*", "+", "*"]),
(
"3 + 4 * 2 / ( 1 - 5 ) ^ 2 ^ 3",
- ["3", "4", "2", "*", "1", "5", "-", "2", "^", "3", "^", "/", "+"],
+ [
+ "3",
+ "4",
+ "2",
+ "*",
+ "1",
+ "5",
+ "-",
+ "2",
+ "^",
+ "3",
+ "^",
+ "/",
+ "+",
+ ],
),
("5", ["5"]),
("+", ["+"]),
diff --git a/timsort/tests.py b/test/test_timsort.py
similarity index 95%
rename from timsort/tests.py
rename to test/test_timsort.py
index 70486e8..04e76aa 100644
--- a/timsort/tests.py
+++ b/test/test_timsort.py
@@ -1,7 +1,6 @@
-import random
import unittest
-from solution import timsort
+from src.timsort.solution import timsort
class TestTimSort(unittest.TestCase):