From e51d0eb35c18e12382441206364ea009ab04a6d6 Mon Sep 17 00:00:00 2001 From: Mihhail Matvejev Date: Wed, 4 Dec 2024 00:38:16 +0300 Subject: [PATCH 1/2] Completing last part of DataStructure task with RB-Tree model --- .github/workflows/ci.yml | 2 +- .idea/etu_algo_labs.iml | 6 +- .idea/misc.xml | 2 +- README.md | 7 +- pyproject.toml | 15 ++ setup.py | 8 + {algo => src/ShuntingYard}/__init__.py | 0 .../ShuntingYard}/solution.py | 8 +- src/algo/__init__.py | 0 algo/LineCheck.py => src/algo/line_check.py | 4 +- algo/Queue.py => src/algo/queue.py | 0 algo/Stack.py => src/algo/stack.py | 0 src/timsort/__init__.py | 0 {timsort => src/timsort}/solution.py | 25 +- src/tree/__init__.py | 0 src/tree/binary_tree.py | 37 +++ src/tree/main.py | 63 +++++ src/tree/nodes.py | 52 ++++ src/tree/parser.py | 34 +++ src/tree/red_black_tree.py | 228 ++++++++++++++++++ src/tree/traversal.py | 82 +++++++ src/tree/tree.txt | 1 + algo/tests.py => test/test_algo.py | 10 +- test/test_binary_tree.py | 44 ++++ test/test_parser.py | 43 ++++ test/test_red_black_tree.py | 206 ++++++++++++++++ .../tests.py => test/test_shunting_yard.py | 18 +- timsort/tests.py => test/test_timsort.py | 3 +- 28 files changed, 861 insertions(+), 37 deletions(-) create mode 100644 pyproject.toml create mode 100644 setup.py rename {algo => src/ShuntingYard}/__init__.py (100%) rename {Shunting Yard => src/ShuntingYard}/solution.py (87%) create mode 100644 src/algo/__init__.py rename algo/LineCheck.py => src/algo/line_check.py (84%) rename algo/Queue.py => src/algo/queue.py (100%) rename algo/Stack.py => src/algo/stack.py (100%) create mode 100644 src/timsort/__init__.py rename {timsort => src/timsort}/solution.py (82%) create mode 100644 src/tree/__init__.py create mode 100644 src/tree/binary_tree.py create mode 100644 src/tree/main.py create mode 100644 src/tree/nodes.py create mode 100644 src/tree/parser.py create mode 100644 src/tree/red_black_tree.py create mode 100644 src/tree/traversal.py create mode 100644 src/tree/tree.txt rename algo/tests.py => test/test_algo.py (90%) create mode 100644 test/test_binary_tree.py create mode 100644 test/test_parser.py create mode 100644 test/test_red_black_tree.py rename Shunting Yard/tests.py => test/test_shunting_yard.py (81%) rename timsort/tests.py => test/test_timsort.py (95%) 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..b752ea1 --- /dev/null +++ b/src/tree/binary_tree.py @@ -0,0 +1,37 @@ +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 + + def _min_value_node(self, 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..a1d69f0 --- /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(filename): + with open(filename, "r") as file: + tree_string = file.read().strip() + parser = TreeParser(tree_string) + root = parser.parse() + return root + + +def build_red_black_tree(binary_root): + rb_tree = RedBlackTree() + + def insert_node(node): + if node: + try: + value = int(node.get_value()) + except ValueError: + value = node.get_value() + rb_tree.insert(value) + insert_node(node.get_left()) + insert_node(node.get_right()) + + insert_node(binary_root) + return rb_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..9d930db --- /dev/null +++ b/test/test_red_black_tree.py @@ -0,0 +1,206 @@ +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) + + def is_valid_red_black_tree(self, 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): From 8eea896e90088d56a95e76fdf720331595be5476 Mon Sep 17 00:00:00 2001 From: Mihhail Matvejev Date: Wed, 4 Dec 2024 00:45:59 +0300 Subject: [PATCH 2/2] fixing filenames --- src/tree/binary_tree.py | 3 ++- src/tree/main.py | 14 +++++++------- test/test_red_black_tree.py | 3 ++- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/tree/binary_tree.py b/src/tree/binary_tree.py index b752ea1..f028169 100644 --- a/src/tree/binary_tree.py +++ b/src/tree/binary_tree.py @@ -30,7 +30,8 @@ def _delete_rec(self, node, value): ) return node - def _min_value_node(self, node): + @staticmethod + def _min_value_node(node): current = node while current.get_left() is not None: current = current.get_left() diff --git a/src/tree/main.py b/src/tree/main.py index a1d69f0..94d4daf 100644 --- a/src/tree/main.py +++ b/src/tree/main.py @@ -9,16 +9,16 @@ ) -def read_tree_from_file(filename): - with open(filename, "r") as file: +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(binary_root): - rb_tree = RedBlackTree() +def build_red_black_tree(bin_root): + tree = RedBlackTree() def insert_node(node): if node: @@ -26,12 +26,12 @@ def insert_node(node): value = int(node.get_value()) except ValueError: value = node.get_value() - rb_tree.insert(value) + tree.insert(value) insert_node(node.get_left()) insert_node(node.get_right()) - insert_node(binary_root) - return rb_tree + insert_node(bin_root) + return tree def demonstrate_traversals(tree): diff --git a/test/test_red_black_tree.py b/test/test_red_black_tree.py index 9d930db..c49ba35 100644 --- a/test/test_red_black_tree.py +++ b/test/test_red_black_tree.py @@ -16,7 +16,8 @@ def setUp(self): for val in self.values: self.tree.insert(val) - def is_valid_red_black_tree(self, tree): + @staticmethod + def is_valid_red_black_tree(tree): """ Checks whether the given red-black tree satisfies all red-black tree properties: