diff --git a/datastax/Trees/avl_tree.py b/datastax/Trees/AVLTree.py similarity index 55% rename from datastax/Trees/avl_tree.py rename to datastax/Trees/AVLTree.py index 61435ac..2b7db52 100644 --- a/datastax/Trees/avl_tree.py +++ b/datastax/Trees/AVLTree.py @@ -1,43 +1,33 @@ -# AVL Tree Implementation (Also: Self Balancing Binary Tree) -from __future__ import annotations - import warnings from typing import Any, Optional - -from datastax.errors import DuplicateNodeWarning -from datastax.Trees.binary_search_tree import BinarySearchTree, TreeNode - - -class AVLNode(TreeNode): - def __init__(self, data: Any, - left: AVLNode = None, - right: AVLNode = None): - super().__init__(data, left, right) - self.height = 1 +from datastax.Utils.Warnings import DuplicateNodeWarning +from datastax.Trees.BinarySearchTree import BinarySearchTree +from datastax.Nodes import AVLNode class AVLTree(BinarySearchTree): - def __init__(self, array: list[Any] = None, root=None): - self._root: Optional[AVLNode] = root - super().__init__(array, root) + def __init__(self, items: Optional[list] = None, + root: Optional[AVLNode] = None): + self.set_root(root) + super().__init__(items, root) # Private helper method for insert function - def _place(self, parent: Optional[AVLNode], data) -> Optional[AVLNode]: + def _place(self, parent: AVLNode | None, data) -> AVLNode | None: if not parent: return AVLNode(data) if parent.data < data: - parent.right = self._place(parent.right, data) + parent.set_right(self._place(parent.right, data)) elif data < parent.data: - parent.left = self._place(parent.left, data) + parent.set_left(self._place(parent.left, data)) else: warnings.warn( f"Insertion unsuccessful. Item '{data}' already exists " "in AVLTree", DuplicateNodeWarning ) - parent.height = 1 + max( + parent.set_height(1 + max( self.height(parent.left), self.height(parent.right) - ) + )) # Balancing the tree return self._balance(parent) @@ -50,11 +40,11 @@ def balance_factor(self, parent: Optional[AVLNode] = None) -> int: # Function to get height of a tree @staticmethod - def height(node: Optional[AVLNode]) -> int: + def height(node: AVLNode | None) -> int: return node.height if node else 0 # Function to balance a node - def _balance(self, parent: Optional[AVLNode]) -> Optional[AVLNode]: + def _balance(self, parent: AVLNode | None) -> AVLNode | None: if not parent: return None balance_factor = self.balance_factor(parent) @@ -66,7 +56,7 @@ def _balance(self, parent: Optional[AVLNode]) -> Optional[AVLNode]: # Perform RL Rotation else: if parent.right: - parent.right = self._right_rotate(parent.right) + parent.set_right(self._right_rotate(parent.right)) return self._left_rotate(parent) if balance_factor > 1: @@ -77,41 +67,41 @@ def _balance(self, parent: Optional[AVLNode]) -> Optional[AVLNode]: # Perform LR Rotation else: if parent.left: - parent.left = self._left_rotate(parent.left) + parent.set_left(self._left_rotate(parent.left)) return self._right_rotate(parent) return parent # Private helper method of balance function to perform RR rotation - def _right_rotate(self, node: AVLNode) -> Optional[AVLNode]: + def _right_rotate(self, node: AVLNode) -> AVLNode | None: left = node.left if left: temp = left.right - left.right = node - node.left = temp - node.height = 1 + max(self.height(node.left), - self.height(node.right)) - left.height = 1 + max(self.height(left.left), - self.height(left.right)) + left.set_right(node) + node.set_left(temp) + node.set_height(1 + max(self.height(node.left), + self.height(node.right))) + left.set_height(1 + max(self.height(left.left), + self.height(left.right))) return left # Private helper method of balance function to perform LL rotation - def _left_rotate(self, node: AVLNode) -> Optional[AVLNode]: + def _left_rotate(self, node: AVLNode) -> AVLNode | None: right = node.right if right: temp = right.left - right.left = node - node.right = temp - node.height = 1 + max(self.height(node.left), - self.height(node.right)) - right.height = 1 + max(self.height(right.left), - self.height(right.right)) + right.set_left(node) + node.set_right(temp) + node.set_height(1 + max(self.height(node.left), + self.height(node.right))) + right.set_height(1 + max(self.height(right.left), + self.height(right.right))) return right - def _delete(self, root: Optional[AVLNode], item: Any) -> Optional[AVLNode]: + def _delete(self, root: AVLNode | None, item: Any) -> AVLNode | None: root = super()._delete(root, item) if root: - root.height = 1 + max( + root.set_height(1 + max( self.height(root.left), self.height(root.right) - ) + )) return self._balance(root) diff --git a/datastax/Trees/AbstractTrees/BinaryTree.py b/datastax/Trees/AbstractTrees/BinaryTree.py new file mode 100644 index 0000000..13eb7d0 --- /dev/null +++ b/datastax/Trees/AbstractTrees/BinaryTree.py @@ -0,0 +1,135 @@ +import math +from typing import Any, Optional +from datastax.Nodes import TreeNode +from datastax.Lists import Queue +from datastax.Utils import Commons +from abc import ABC as AbstractClass, abstractmethod + + +class BinaryTree(AbstractClass): + _root: Optional[TreeNode] + _string: Optional[str] + + @property + def root(self): + return self._root + + @property # Level Order Traversal -> Tree to array + def array_repr(self) -> list[Any]: + array = [] + queue = Queue() + if self.root: + queue.enqueue(self.root) + while not queue.is_empty(): + node = queue.dequeue() + array.append(node.data) + if node.left: + queue.enqueue(node.left) + if node.right: + queue.enqueue(node.right) + + return array + + # Level order Traversal of Tree + def __str__(self): + root = self.root + if not root: + return " NULL" + + lines = [] + level = [root] + nodes = 1 + max_width = 0 + while nodes: + line = [] + next_level = [] + nodes = 0 + for node in level: + if node: + data = Commons.repr(node.data) + max_width = max(len(data), max_width) + line.append(data) + next_level += [node.left, node.right] + if node.left: + nodes += 1 + if node.right: + nodes += 1 + continue + line.append(None) + next_level += [None] * 2 + if max_width % 2: + max_width += 1 + lines.append(line) + level = next_level + ################################################################## + "Building string from calculated values" + per_piece = len(lines[-1]) * (max_width + 4) + + string_builder = f"{Commons.node_builder(lines[0][0], per_piece)}\n" + per_piece //= 2 + for _, line in enumerate(lines[1:], 1): + hpw = int(math.floor(per_piece / 2) - 1) + # Printing ┌ ┴ ┐ or ┌ ─ ┘ or └ ─ ┐ components + for j, value in enumerate(line): + string_builder += ( + ( + ("┴" if value else "┘") + if line[j - 1] + else ("└" if value else " ") + ) + if j % 2 + else " " + ) + + if not value: + string_builder += " " * (per_piece - 1) + continue + if j % 2: + string_builder += f"{'─' * hpw}┐{' ' * hpw}" + else: + string_builder += f"{' ' * hpw}┌{'─' * hpw}" + string_builder += "\n" + + # Printing the value of each Node + for value in line: + string_builder += Commons.node_builder(value, per_piece) + string_builder += "\n" + per_piece //= 2 + + return string_builder + + # Pre Order Traversal of Tree + def preorder_print(self) -> None: + def string_builder(parent: Optional[TreeNode], has_right_child: bool, + padding="", component="") -> None: + if not parent: + return + if self._string is not None: + self._string += ( + f"\n{padding}{component}" f"{Commons.repr(parent.data)}" + ) + if parent is not root: + padding += "│ " if has_right_child else " " + left_pointer = "├─▶ " if parent.right else "└─▶ " + right_pointer = "└─▶ " + string_builder( + parent.left, bool(parent.right), padding, left_pointer + ) + string_builder(parent.right, False, padding, right_pointer) + + root = self.root + if not root: + self._string = "NULL" + print(self._string) + return + self._string = "" + string_builder(root, bool(root.right)) + print(self._string) + + @abstractmethod + def insert(self, item: Any): + ... + + @abstractmethod + def set_root(self, root: TreeNode): + ... diff --git a/datastax/Trees/AbstractTrees/HuffmanTree.py b/datastax/Trees/AbstractTrees/HuffmanTree.py new file mode 100644 index 0000000..4e5f04a --- /dev/null +++ b/datastax/Trees/AbstractTrees/HuffmanTree.py @@ -0,0 +1,175 @@ +import math +from typing import Optional +from abc import ABC as AbstractClass, abstractmethod +from datastax.Utils import Commons +from datastax.Nodes import HuffmanNode +from datastax.Tables import HuffmanTable +from datastax.Trees.AbstractTrees.BinaryTree import BinaryTree + + +class HuffmanTree(BinaryTree, AbstractClass): + _data: Optional[list[str] | str] + _table: Optional[HuffmanTable] = None + _huffman_code = "" + + @property + def huffman_table(self): + return self._table + + @property + def huffman_code(self): + return self._huffman_code + + # Level order Traversal of Tree + def __str__(self): # noqa: C901 + root = self.root + if not root: + return " NULL" + + lines = [] + level = [root] + nodes = 1 + max_width = 0 + while nodes: + line = [] + next_level = [] + nodes = 0 + for node in level: + if node: + data = Commons.repr(node.data or node.frequency) + frequency = "│" + if not any([node.left, node.right]): + frequency = f"{node.frequency}" + max_width = max(len(data), max_width) + line.append([data, frequency]) + next_level += [node.left, node.right] + if node.left: + nodes += 1 + if node.right: + nodes += 1 + continue + line.append(None) + next_level += [None] * 2 + if max_width % 2: + max_width += 1 + lines.append(line) + level = next_level + ################################################################## + "Building string from calculated values" + per_piece = len(lines[-1]) * (max_width + 4) + string_builder = f"{Commons.node_builder(lines[0][0][0], per_piece)}\n" + string_builder += Commons.node_builder(" 0", per_piece // 2) + string_builder = ( + f"{string_builder[:]}" f"{Commons.node_builder(lines[0][0][1], 1)}" + ) + string_builder += ( + Commons.node_builder("1 ", per_piece // 2 - 1) + "\n" + ) + per_piece //= 2 + for _, line in enumerate(lines[1:], 1): + hpw = int(math.floor(per_piece / 2) - 1) + # Printing ┌ ┴ ┐ or ┌ ─ ┘ or └ ─ ┐ components + for j, value in enumerate(line): + string_builder += ( + ( + ("┴" if value else "┘") + if line[j - 1] + else ("└" if value else " ") + ) + if j % 2 + else " " + ) + + if not value: + string_builder += " " * (per_piece - 1) + continue + if j % 2: + string_builder += f"{'─' * hpw}┐{' ' * hpw}" + else: + string_builder += f"{' ' * hpw}┌{'─' * hpw}" + string_builder += "\n" + + # Printing the value of each Node + for value in line: + value = value[0] if value else value + string_builder += Commons.node_builder(value, per_piece) + string_builder += "\n" + for value in line: + internal = value and value[1] == "│" + if internal: + string_builder += ( + Commons.node_builder(" 0", per_piece // 2) + ) + data = f"{value[1]}" if value else value + piece_width = 1 if internal else per_piece + string_builder = ( + f"{string_builder[:] if internal else string_builder}" + f"{Commons.node_builder(data, piece_width)}" + ) + if value and value[1] == "│": + string_builder += ( + Commons.node_builder("1 ", per_piece // 2 - 1) + ) + + string_builder += "\n" + per_piece //= 2 + + return string_builder + + # Pre Order Traversal of Tree + def preorder_print(self) -> None: + def string_builder( + parent: Optional[HuffmanNode], + has_right_child: bool, + padding="", + component="", + ) -> None: + if not parent: + return + if self._string is not None: + self._string += f"\n{padding}{component}" + data = parent.data + if not data: + data = parent.frequency + self._string += str(data) + else: + data = Commons.repr(data) + self._string += f"{data} [{parent.frequency}]" + if parent is not root: + padding += "│ " if has_right_child else " " + left_pointer = "├─▶ " if parent.right else "└─▶ " + right_pointer = "└─▶ " + string_builder(parent.left, bool(parent.right), + padding, left_pointer) + string_builder(parent.right, False, + padding, right_pointer) + + root = self.root + if not root: + self._string = "NULL" + print(self._string) + return + self._string = "" + string_builder(root, bool(root.right)) + print(self._string) + + @abstractmethod + def huffman_code_of(self, character: str): + ... + + @abstractmethod + def size_calculator(self): + ... + + @abstractmethod + def compression_ratio(self): + ... + + @abstractmethod + def space_saved(self): + ... + + @staticmethod + @abstractmethod + def decode_from_table(huffman_code: str, huffman_table: dict) -> str: + ... diff --git a/datastax/Trees/AbstractTrees/RedBlackTree.py b/datastax/Trees/AbstractTrees/RedBlackTree.py new file mode 100644 index 0000000..141547a --- /dev/null +++ b/datastax/Trees/AbstractTrees/RedBlackTree.py @@ -0,0 +1,142 @@ +from itertools import chain +from typing import Optional +from datastax.Nodes import RedBlackNode +from datastax.Utils import Commons, Colors +from datastax.Trees.AbstractTrees.BinaryTree import BinaryTree +from abc import ABC as AbstractClass, abstractmethod + +fore, back, reset = Colors.FORE, Colors.BACK, Colors.RESET +red, black, grey = Colors.RED, Colors.BLACK, Colors.GREY + + +class RedBlackTree(BinaryTree, AbstractClass): + + @staticmethod + def _nodes_level_wise(root: RedBlackNode) -> list[list]: + level: list = [root] + nodes: int = 1 + levels: list[list] = [] + while nodes: + current_level: list[Optional[RedBlackNode]] = [] + next_level = [] + nodes = 0 + for node in level: + if not node: + current_level.append(None) + next_level += [None] * 2 + continue + current_level.append(node) + next_level += [node.left, node.right] + if node.left: + nodes += 1 + if node.right: + nodes += 1 + levels.append(current_level) + level = next_level + return levels + + @staticmethod + def _maximum_width(levels: list[list]) -> int: + max_width = 0 + for node in filter(bool, chain(*levels)): + data = Commons.repr(node.data) + max_width = max(max_width, len(data) + 4) + return max_width + 1 if max_width % 2 else max_width + + # Level order Traversal of Tree + def __str__(self): + root = self.root + if not root: + return " NULL" + levels = self._nodes_level_wise(root) + max_width = self._maximum_width(levels) + 1 + padding = 4 + per_piece = len(levels[-1]) * max_width + extra_line = f"{back}{grey}{' ' * (per_piece + padding)}{reset}" + strings = [extra_line] + for level in levels: + # printing the data first + data_string = f'{back}{grey}' + for node in level: + data = '' + if node: + data = Commons.repr(node.data) + data = Commons.format(node.color, data) + data = Commons.redblack_node_builder(data, per_piece) + data_string += data + per_piece //= 2 + data_string += f" {reset}" + + # printing the piece + piece_string = f'{back}{grey}' + per_node = per_piece // 2 + for node in level: + piece = ' ' * max_width + if node: + if node.left and node.right: + piece = ( + f"┌{'─' * (per_node - 1)}┴{'─' * (per_node - 1)}┐" + ) + elif node.left: + piece = ( + f" ┌{'─' * (per_node - 1)}┘{' ' * (per_node + 1)}" + ) + elif node.right: + piece = ( + f"{' ' * (per_node + 1)}└{'─' * (per_node - 1)}┐" + ) + piece = Commons.redblack_node_builder( + piece, per_piece * 2, len(piece) + ) + piece_string += piece + + piece_string += f" {reset}" + strings += [data_string, piece_string] + return '\n'.join(strings) + + # Pre Order Traversal of Tree + def preorder_print(self) -> None: + def string_builder(parent: Optional[RedBlackNode], + has_right_child: bool, + padding="", component="") -> None: + if not parent: + return + data = f'{back}{grey}' + padding += f'{back}{grey}' + if self._string is not None: + data += Commons.format(parent.color, Commons.repr(parent.data)) + data += f" {reset}" + self._string += f"\n{padding}{component}{Commons.repr(data)}" + if parent is not root: + padding += "│ " if has_right_child else " " + left_pointer = "├─▶ " if parent.right else "└─▶ " + right_pointer = "└─▶ " + string_builder(parent.left, bool(parent.right), padding, + left_pointer) + string_builder(parent.right, False, padding, right_pointer) + + root = self.root + if not root: + self._string = "NULL" + print("NULL") + return + self._string = "" + string_builder(root, bool(root.right)) + print(self._string) + + @abstractmethod + def _left_rotate(self, node: RedBlackNode) -> RedBlackNode: + ... + + @abstractmethod + def _right_rotate(self, node: RedBlackNode) -> RedBlackNode: + ... + + @abstractmethod + def _resolve_left_black_conflict(self, node: RedBlackNode) -> RedBlackNode: + ... + + @abstractmethod + def _resolve_right_black_conflict(self, + node: RedBlackNode) -> RedBlackNode: + ... diff --git a/datastax/Trees/AbstractTrees/segment_tree.py b/datastax/Trees/AbstractTrees/SegmentTree.py similarity index 66% rename from datastax/Trees/AbstractTrees/segment_tree.py rename to datastax/Trees/AbstractTrees/SegmentTree.py index bc1a1fd..26aa7eb 100644 --- a/datastax/Trees/AbstractTrees/segment_tree.py +++ b/datastax/Trees/AbstractTrees/SegmentTree.py @@ -1,61 +1,36 @@ -from __future__ import annotations - import math -from typing import Any, Optional - -from datastax.Trees.AbstractTrees.binary_tree import ( - BinaryTree, TreeNode, _node_builder, _mangled -) - +from typing import Optional +from abc import ABC as AbstractClass, abstractmethod +from datastax.Utils import Commons +from datastax.Nodes import SegmentNode +from datastax.Trees.AbstractTrees.BinaryTree import BinaryTree -class SegmentNode(TreeNode): - def __init__(self, data: Any, - left: SegmentNode = None, - right: SegmentNode = None): - self.left_index = 0 - self.right_index = 0 - super().__init__(data, left, right) - -class SegmentTree(BinaryTree): - def insert(self, item: Any): - raise NotImplementedError +class SegmentTree(BinaryTree, AbstractClass): + _segment_array: Optional[list] @property def segment_array(self): - segment_array = [] - self._traverse_leafs(self.root, segment_array) - return segment_array - - def _traverse_leafs(self, node, array): - if not node: - return - if not any((node.left, node.right)): - array.append(node.data) - return - # if left child is found, check for leaf node recursively - if node.left: - self._traverse_leafs(node.left, array) - # if right child is found, check for leaf node recursively - if node.right: - self._traverse_leafs(node.right, array) + self._segment_array = [] + self._traverse_leafs(self.root) + return self._segment_array def __str__(self): # noqa: C901 root = self.root if not root: return " NULL" - lines: list[list] = [] - level: list[Optional[SegmentNode]] = [root] - nodes: int = 1 - max_width: int = 0 + lines = [] + level = [root] + nodes = 1 + max_width = 0 while nodes: - line: list[Optional[list]] = [] - next_level: list[Optional[SegmentNode]] = [] + line = [] + next_level = [] nodes = 0 for node in level: if node: - data = _mangled(node.data) + data = Commons.repr(node.data) _range = None if node.left_index != node.right_index: _range = f"[{node.left_index}:{node.right_index}]" @@ -77,8 +52,9 @@ def __str__(self): # noqa: C901 "Building string from calculated values" per_piece = len(lines[-1]) * (max_width + 4) - string_builder = f"{_node_builder(lines[0][0][0], per_piece)}\n" - string_builder += f"{_node_builder(lines[0][0][1], per_piece)}\n" + string_builder = f"{Commons.node_builder(lines[0][0][0], per_piece)}\n" + string_builder += \ + f"{Commons.node_builder(lines[0][0][1], per_piece)}\n" per_piece //= 2 for _, line in enumerate(lines[1:], 1): hpw = int(math.floor(per_piece / 2) - 1) @@ -100,11 +76,11 @@ def __str__(self): # noqa: C901 # Printing the value of each Node for value in line: value = value[0] if value else value - string_builder += _node_builder(value, per_piece) + string_builder += Commons.node_builder(value, per_piece) string_builder += '\n' for value in line: value = value[1] if value else value - string_builder += _node_builder(value, per_piece) + string_builder += Commons.node_builder(value, per_piece) string_builder += '\n' per_piece //= 2 @@ -120,7 +96,7 @@ def string_builder(parent: Optional[SegmentNode], if self._string is not None: self._string += ( f"\n{padding}{component}" - f"{_mangled(parent.data)} " + f"{Commons.repr(parent.data)} " ) if parent.left_index != parent.right_index: self._string += ( @@ -142,3 +118,21 @@ def string_builder(parent: Optional[SegmentNode], self._string = "" string_builder(root, bool(root.right)) print(self._string) + + @abstractmethod + def update_at_range(self, left: int, right: int, data: int) -> None: + ... + + @abstractmethod + def update_at_index(self, index: int, data: int) -> None: + ... + + @abstractmethod + def get_range(self, left: int, right: int, + root: SegmentNode | None, + lazy_node: SegmentNode | None): + ... + + @abstractmethod + def _traverse_leafs(self, node: SegmentNode | None) -> None: + ... diff --git a/datastax/Trees/AbstractTrees/threaded_binary_tree.py b/datastax/Trees/AbstractTrees/ThreadedBinaryTree.py similarity index 50% rename from datastax/Trees/AbstractTrees/threaded_binary_tree.py rename to datastax/Trees/AbstractTrees/ThreadedBinaryTree.py index 827d67e..77b0ed7 100644 --- a/datastax/Trees/AbstractTrees/threaded_binary_tree.py +++ b/datastax/Trees/AbstractTrees/ThreadedBinaryTree.py @@ -1,143 +1,12 @@ -# Private module to separate print logic from insertion logic -from __future__ import annotations - from itertools import chain from typing import Optional, Any +from abc import ABC as AbstractClass, abstractmethod +from datastax.Utils import Commons +from datastax.Trees.AbstractTrees.BinaryTree import BinaryTree +from datastax.Nodes import ThreadedNode -from datastax.Lists import Queue -from datastax.Trees import ( - BinaryTree, - AVLTree, - HeapTree, - MinHeapTree -) -from datastax.Trees.AbstractTrees import binary_tree -from datastax.Trees.AbstractTrees.binary_tree import TreeNode, _mangled - - -class ThreadedNode(TreeNode): - def __init__(self, data: Any, - left: ThreadedNode = None, - right: ThreadedNode = None) -> None: - super().__init__(data, left, right) - self.left_is_child = bool(self.left) - self.right_is_child = bool(self.right) - - def __str__(self): - values = [self.data, self.left.data, self.right.data] - values = list( - map(lambda value: "" if value is None else _mangled(value), values) - ) - max_width = max(len(_mangled(data)) for data in values if data) - max_width = (max_width + 1) if max_width % 2 else max_width - # To make max_width even - padding = 6 - per_piece = (max_width + padding) * 2 - # Building the part first - wpn = per_piece // 2 - 1 - wpn = (wpn + 1) if wpn % 2 else wpn - piece = '┴'.center(wpn, '─') - piece = f"{'┌' if self.left_is_child else '└'}{piece[:-1]}" - piece = f"{piece}{'┐' if self.right_is_child else '┘'}" - piece = piece.center(per_piece) + '\n' - - root = values[0] - left = values[1].center(wpn) - right = f"{values[2].center(wpn)}\n" - if self.left_is_child: - if self.right_is_child: - _string = ( - f"{root.center(wpn - 1)}".center(per_piece) + - f"\n{piece}{left}{right}" - ) - else: - _string = ( - f"{' ' * len(left)}{right}" - f"{root.center(wpn)}│".center(per_piece) + - f"\n{piece}{left}" - ) - else: - _string = left - if self.right_is_child: - _string += ( - f"\n│{root.center(wpn)}".center(per_piece) + - f"\n{piece}{' ' * len(left)}{right}" - ) - else: - _string += ( - f"{right}" - f"│{root.center(wpn - 1)}│".center(per_piece) + - f"\n{piece}" - ) - return _string - - def preorder_print(self) -> str: - values = [self.data, self.left.data if self.left_is_child else None, - self.right.data if self.right_is_child else None] - values = list( - map(lambda value: "" if value is None else _mangled(value), values) - ) - string_builder = f'{values[0]}\n' - if any(values[1:]): - if all(values[1:]): - string_builder += f"├─▶ {values[1]}\n" - string_builder += f"└─▶ {values[2]}" - else: - string_builder += f"└─▶ {values[1] or values[2]}" - - return string_builder - - -class ThreadedBinaryTree(binary_tree.BinaryTree): - def __init__(self, array=None, root: ThreadedNode = None, *, - insertion_logic: str = None - ): - self._root: Optional[ThreadedNode] = root - self.head = self.tail = self.root - self.dummy_node = ThreadedNode(None, self.root) - self.dummy_node.right = self.dummy_node - - self.tree: Any = self - if insertion_logic is None or insertion_logic.lower() in ( - 'threadedbinarytree', 'binarysearchtree'): - super().__init__(array, root) - elif insertion_logic.lower() == "binarytree": - self.tree = BinaryTree(array) - elif insertion_logic.lower() == "avltree": - self.tree = AVLTree(array) - elif insertion_logic.lower() == "heaptree": - self.tree = HeapTree(array) - elif insertion_logic.lower() == "minheaptree": - self.tree = MinHeapTree(array) - else: # else Construct tree using BinarySearchTree Logic - print("This tree is has no insertion logic.", - "Building tree using generic BinarySearchTree Logic") - super().__init__(array, root) - if not isinstance(self.tree, ThreadedBinaryTree): - self.convert_to_tbt(self.tree.root) - - def convert_to_tbt(self, root) -> None: - raise NotImplementedError - - @property # Level Order Traversal -> Tree to array - def array_repr(self) -> list[Any]: - array = [] - queue = Queue() - if self.root: - queue.enqueue(self.root) - while not queue.is_empty(): - node = queue.dequeue() - array.append(node.data) - if node.left_is_child: - queue.enqueue(node.left) - if node.right_is_child: - queue.enqueue(node.right) - - return array - - def insert(self, data: Any, root: ThreadedNode = None) -> None: - raise NotImplementedError +class ThreadedBinaryTree(BinaryTree, AbstractClass): # Level order Traversal of Tree def __str__(self): # noqa: C901 @@ -148,12 +17,12 @@ def __str__(self): # noqa: C901 max_width = self._maximum_width(levels) # get the maximum_with of data padding = 6 per_piece = len(levels[-1]) * (max_width + padding) * 2 - strings: list[str] = [] + strings = [] for level in levels: # Constructing the data line data_string = "" for node in level: - data = _mangled(node.data) if node else '' + data = Commons.repr(node.data) if node else '' data = data.center(per_piece) data_string += data @@ -188,7 +57,7 @@ def string_builder(parent: Optional[ThreadedNode], if self._string is not None: self._string += ( f"\n{padding}{component}" - f"{_mangled(parent.data)}" + f"{Commons.repr(parent.data)}" ) if parent is not root: padding += "│ " if has_right_child else " " @@ -239,7 +108,9 @@ def _nodes_level_wise(root: ThreadedNode) -> list[list]: @staticmethod def _maximum_width(levels: list[list]) -> int: - data = [_mangled(node.data) for node in filter(bool, chain(*levels))] + data = [ + Commons.repr(node.data) for node in filter(bool, chain(*levels)) + ] max_width = max(map(len, data)) return max_width + 1 if max_width % 2 else max_width @@ -281,3 +152,11 @@ def _draw_header(self, strings: list[str]) -> None: bottom += f"{' ' * (last - len(bottom))}│" for string in [bottom, middle, top]: strings.insert(0, string) + + @abstractmethod + def inorder(self) -> list[Any]: + ... + + @abstractmethod + def convert_from(self, root) -> None: + ... diff --git a/datastax/Trees/AbstractTrees/TreeNode.py b/datastax/Trees/AbstractTrees/TreeNode.py deleted file mode 100644 index e69de29..0000000 diff --git a/datastax/Trees/AbstractTrees/__init__.py b/datastax/Trees/AbstractTrees/__init__.py index e69de29..aa2b9d2 100644 --- a/datastax/Trees/AbstractTrees/__init__.py +++ b/datastax/Trees/AbstractTrees/__init__.py @@ -0,0 +1,13 @@ +from .BinaryTree import BinaryTree +from .RedBlackTree import RedBlackTree +from .HuffmanTree import HuffmanTree +from .SegmentTree import SegmentTree +from .ThreadedBinaryTree import ThreadedBinaryTree + +__all__ = [ + 'BinaryTree', + 'RedBlackTree', + 'HuffmanTree', + 'SegmentTree', + 'ThreadedBinaryTree' +] diff --git a/datastax/Trees/AbstractTrees/binary_tree.py b/datastax/Trees/AbstractTrees/binary_tree.py deleted file mode 100644 index e76b9fb..0000000 --- a/datastax/Trees/AbstractTrees/binary_tree.py +++ /dev/null @@ -1,220 +0,0 @@ -# Private module to separate print logic from main logic of BinaryTree -from __future__ import annotations - -import math -from typing import Any, Optional - -from datastax.Lists import Queue - - -def _node_builder(data: Optional[str], piece_width: int) -> str: - value: str = data or '' - gap1 = int(math.ceil(piece_width / 2 - len(value) / 2)) - gap2 = int(math.floor(piece_width / 2 - len(value) / 2)) - return f"{' ' * gap1}{value}{' ' * gap2}" - - -# private method to mangle string __repr__ -def _mangled(item: Any) -> str: - if '\n' in str(item): - return f"{str(type(item))[8:-2].split('.')[-1]}@{id(item)}" - return str(item) - - -class TreeNode: - def __init__(self, data: Any, - left=None, - right=None) -> None: - self.left = left - self.data = data - self.right = right - - def __str__(self): - values = [self.data, - self.left.data if self.left else None, - self.right.data if self.right else None] - values = list( - map(lambda value: "" if value is None else _mangled(value), values) - ) - max_width = max(len(_mangled(data)) for data in values if data) - if max_width % 2: - max_width += 1 # To make max_width even - - "Building string from calculated values" - per_piece = 2 * (max_width + 4) - string_builder = f"{_node_builder(values[0], per_piece)}\n" - per_piece //= 2 - hpw = int(per_piece // 2 - 1) - if any(values[1:]): - if all(values[1:]): - string_builder += ( - f"{' ' * (hpw + 1)}" - f"┌{'─' * hpw}┴{'─' * hpw}┐\n" - ) - string_builder += _node_builder( - values[1], per_piece - ) + _node_builder(values[2], per_piece) - elif values[1]: - string_builder += f"{' ' * (hpw + 1)}┌{'─' * hpw}┘\n" - string_builder += _node_builder(values[1], per_piece) - else: - string_builder += f"{' ' * (per_piece - 1)} └{'─' * hpw}┐\n" - string_builder += ( - f"{' ' * (per_piece - 1)} " - f"{_node_builder(values[2], per_piece)}" - ) - - return string_builder - - def preorder_print(self) -> str: - values = [self.data, self.left.data if self.left else None, - self.right.data if self.right else None] - values = list( - map(lambda value: "" if value is None else _mangled(value), values) - ) - - string_builder = f'{values[0]}\n' - if any(values[1:]): - if all(values[1:]): - string_builder += f"├─▶ {values[1]}\n" - string_builder += f"└─▶ {values[2]}" - else: - string_builder += f"└─▶ {values[1] or values[2]}" - - return string_builder - - def __repr__(self): - return self.__str__() - - -class BinaryTree: - def __init__(self, array=None, root=None): - self._root = root - self._construct(array) - self._string: Optional[str] = None - - @property - def root(self): - return self._root - - def _construct(self, array=None): - if not array or array[0] is None: - return None - for item in array: - try: - self.insert(item) - except TypeError as error: - raise error - return self - - def insert(self, item: Any): - raise NotImplementedError - - @property # Level Order Traversal -> Tree to array - def array_repr(self) -> list[Any]: - array = [] - queue = Queue() - if self.root: - queue.enqueue(self.root) - while not queue.is_empty(): - node = queue.dequeue() - array.append(node.data) - if node.left: - queue.enqueue(node.left) - if node.right: - queue.enqueue(node.right) - - return array - - # Level order Traversal of Tree - def __str__(self): # noqa: C901 - root = self.root - if not root: - return " NULL" - - lines: list[list[Optional[str]]] = [] - level: list[Optional[TreeNode]] = [root] - nodes: int = 1 - max_width: int = 0 - while nodes: - line: list[Optional[str]] = [] - next_level: list[Optional[TreeNode]] = [] - nodes = 0 - for node in level: - if node: - data = _mangled(node.data) - max_width = max(len(data), max_width) - line.append(data) - next_level += [node.left, node.right] - if node.left: - nodes += 1 - if node.right: - nodes += 1 - continue - line.append(None) - next_level += [None] * 2 - if max_width % 2: - max_width += 1 - lines.append(line) - level = next_level - ################################################################## - "Building string from calculated values" - per_piece = len(lines[-1]) * (max_width + 4) - - string_builder = f"{_node_builder(lines[0][0], per_piece)}\n" - per_piece //= 2 - for _, line in enumerate(lines[1:], 1): - hpw = int(math.floor(per_piece / 2) - 1) - # Printing ┌ ┴ ┐ or ┌ ─ ┘ or └ ─ ┐ components - for j, value in enumerate(line): - string_builder += ( - ('┴' if value else '┘') if line[j - 1] else ( - '└' if value else ' ')) if j % 2 else ' ' - - if not value: - string_builder += ' ' * (per_piece - 1) - continue - if j % 2: - string_builder += f"{'─' * hpw}┐{' ' * hpw}" - else: - string_builder += f"{' ' * hpw}┌{'─' * hpw}" - string_builder += '\n' - - # Printing the value of each Node - for value in line: - string_builder += _node_builder(value, per_piece) - string_builder += '\n' - per_piece //= 2 - - return string_builder - - # Pre Order Traversal of Tree - def preorder_print(self) -> None: - def string_builder(parent: Optional[TreeNode], has_right_child: bool, - padding="", component="") -> None: - if not parent: - return - if self._string is not None: - self._string += ( - f"\n{padding}{component}" - f"{_mangled(parent.data)}" - ) - if parent is not root: - padding += "│ " if has_right_child else " " - left_pointer = "├─▶ " if parent.right else "└─▶ " - right_pointer = "└─▶ " - string_builder(parent.left, bool(parent.right), padding, - left_pointer) - string_builder(parent.right, False, padding, right_pointer) - - root = self.root - if not root: - self._string = "NULL" - print(self._string) - return - self._string = "" - string_builder(root, bool(root.right)) - print(self._string) - - def __repr__(self): - return self.__str__() diff --git a/datastax/Trees/AbstractTrees/huffman_tree.py b/datastax/Trees/AbstractTrees/huffman_tree.py deleted file mode 100644 index a2e0b9a..0000000 --- a/datastax/Trees/AbstractTrees/huffman_tree.py +++ /dev/null @@ -1,272 +0,0 @@ -# Private module to separate print logic from main logic of HuffmanTree -from __future__ import annotations - -import math -from typing import Any, Optional, Union - -from datastax.Trees.AbstractTrees.binary_tree import ( - BinaryTree, TreeNode, - _node_builder, _mangled -) - - -class HuffmanNode(TreeNode): - def __init__(self, data: Any, - left: HuffmanNode = None, - right: HuffmanNode = None, - frequency: int = 1): - self.frequency = frequency - super().__init__(data, left, right) - - def __str__(self): - values = [ - self.data or self.frequency, - self.left.data or self.left.frequency if self.left else None, - self.right.data or self.right.frequency if self.right else None - ] - values = list( - map(lambda value: "" if value is None else _mangled(value), values) - ) - max_width = max(len(_mangled(data)) for data in values if data) - if max_width % 2: - max_width += 1 # To make max_width even - - "Building string from calculated values" - per_piece = 2 * (max_width + 4) - string_builder = f"{_node_builder(values[0], per_piece)}\n" - per_piece //= 2 - hpw = int(per_piece // 2 - 1) - if any(values[1:]): - if all(values[1:]): - string_builder += ( - f"{' ' * (hpw + 1)}" - f"┌{'─' * hpw}┴{'─' * hpw}┐\n" - ) - string_builder += _node_builder( - values[1], per_piece - ) + _node_builder(values[2], per_piece) - elif values[1]: - string_builder += f"{' ' * (hpw + 1)}┌{'─' * hpw}┘\n" - string_builder += _node_builder(values[1], per_piece) - else: - string_builder += f"{' ' * (per_piece - 1)} └{'─' * hpw}┐\n" - string_builder += ( - f"{' ' * (per_piece - 1)} " - f"{_node_builder(values[2], per_piece)}" - ) - - return string_builder - - def preorder_print(self) -> str: - values = [ - self.data or self.frequency, - self.left.data or self.left.frequency if self.left else None, - self.right.data or self.right.frequency if self.right else None - ] - values = list( - map(lambda value: "" if value is None else _mangled(value), values) - ) - - string_builder = f'{values[0]}\n' - if any(values[1:]): - if all(values[1:]): - string_builder += f"├─▶ {values[1]}\n" - string_builder += f"└─▶ {values[2]}" - else: - string_builder += f"└─▶ {values[1] or values[2]}" - - return string_builder - - -class HuffmanTree(BinaryTree): - def __init__(self, data: Union[list[str], str] = None): - self._data: Union[list[str], str, None] = data - self._huffman_code = '' - self._table = None - super().__init__(data) - - @property - def huffman_table(self) -> Optional[HuffmanTable]: - return self._table - - @property - def huffman_code(self): - return self._huffman_code - - # Level order Traversal of Tree - def __str__(self): # noqa: C901 - root = self.root - if not root: - return " NULL" - - lines: list[list] = [] - level: list[Optional[HuffmanNode]] = [root] - nodes: int = 1 - max_width: int = 0 - while nodes: - line: list[Optional[list]] = [] - next_level: list[Optional[HuffmanNode]] = [] - nodes = 0 - for node in level: - if node: - data = _mangled(node.data or node.frequency) - frequency = '│' - if not any([node.left, node.right]): - frequency = f"{node.frequency}" - max_width = max(len(data), max_width) - line.append([data, frequency]) - next_level += [node.left, node.right] - if node.left: - nodes += 1 - if node.right: - nodes += 1 - continue - line.append(None) - next_level += [None] * 2 - if max_width % 2: - max_width += 1 - lines.append(line) - level = next_level - ################################################################## - "Building string from calculated values" - per_piece = len(lines[-1]) * (max_width + 4) - string_builder = f"{_node_builder(lines[0][0][0], per_piece)}\n" - string_builder += _node_builder(' 0', per_piece // 2) - string_builder = ( - f"{string_builder[:]}" - f"{_node_builder(lines[0][0][1], 1)}" - ) - string_builder += _node_builder('1 ', per_piece // 2 - 1) + '\n' - per_piece //= 2 - for _, line in enumerate(lines[1:], 1): - hpw = int(math.floor(per_piece / 2) - 1) - # Printing ┌ ┴ ┐ or ┌ ─ ┘ or └ ─ ┐ components - for j, value in enumerate(line): - string_builder += ( - ('┴' if value else '┘') if line[j - 1] else ( - '└' if value else ' ')) if j % 2 else ' ' - - if not value: - string_builder += ' ' * (per_piece - 1) - continue - if j % 2: - string_builder += f"{'─' * hpw}┐{' ' * hpw}" - else: - string_builder += f"{' ' * hpw}┌{'─' * hpw}" - string_builder += '\n' - - # Printing the value of each Node - for value in line: - value = value[0] if value else value - string_builder += _node_builder(value, per_piece) - string_builder += '\n' - for value in line: - internal = value and value[1] == '│' - if internal: - string_builder += _node_builder(' 0', per_piece // 2) - data = f"{value[1]}" if value else value - string_builder = ( - f"{string_builder[:] if internal else string_builder}" - f"{_node_builder(data, 1 if internal else per_piece)}" - ) - if value and value[1] == '│': - string_builder += _node_builder('1 ', - per_piece // 2 - 1) - - string_builder += '\n' - per_piece //= 2 - - return string_builder - - # Pre Order Traversal of Tree - def preorder_print(self) -> None: - def string_builder(parent: Optional[HuffmanNode], - has_right_child: bool, - padding="", component="") -> None: - if not parent: - return - if self._string is not None: - self._string += f"\n{padding}{component}" - data = parent.data - if not data: - data = parent.frequency - self._string += str(data) - else: - data = _mangled(data) - self._string += f"{data} [{parent.frequency}]" - if parent is not root: - padding += "│ " if has_right_child else " " - left_pointer = "├─▶ " if parent.right else "└─▶ " - right_pointer = "└─▶ " - string_builder(parent.left, bool(parent.right), padding, - left_pointer) - string_builder(parent.right, False, padding, right_pointer) - - root = self.root - if not root: - self._string = "NULL" - print(self._string) - return - self._string = "" - string_builder(root, bool(root.right)) - print(self._string) - - def insert(self, item: Any): - raise NotImplementedError - - -class HuffmanTable: - def __init__(self, table: dict[str, str], - frequencies: dict[str, int]): - self._huffman_table = table - self.frequency = frequencies - self._size = 0 - self._calculate_size() - - @property - def data(self) -> dict[str, str]: - return self._huffman_table - - @property - def size(self): - return self._size - - def _calculate_size(self): - raise NotImplementedError - - def __str__(self): - items = self.data - padding = 4 - max_width = max(len(code) for *_, code in items.values()) + padding * 2 - if max_width < 10: - max_width = 12 - mid_width = max_width * 2 - (4 if max_width > 12 else 0) - - h_border = f"╔{'═' * max_width}╤{'═' * mid_width}╤{'═' * max_width}╗\n" - header = ( - f"║{'Unique'.center(max_width)}" - f"│{'Occurrence /'.center(mid_width)}│" - f"{'Huffman'.center(max_width)}║\n" - - f"║{'Characters'.center(max_width)}" - f"│{'Frequency'.center(mid_width)}│" - f"{'Code'.center(max_width)}║\n" - ) - sep = f"╟{'─' * max_width}┼{'─' * mid_width}┼{'─' * max_width}╢\n" - data_template = "║{}│{}│{}║\n" - - body = '' - for character, huffman_code in items.items(): - body += sep - body += data_template.format(character.center(max_width), - str( - self.frequency[character] - ).center(mid_width), - huffman_code.rjust( - max_width - padding).center( - max_width)) - f_border = f"╚{'═' * max_width}╧{'═' * mid_width}╧{'═' * max_width}╝" - return h_border + header + body + f_border - - def __repr__(self): - return self.__str__() diff --git a/datastax/Trees/AbstractTrees/red_black_tree.py b/datastax/Trees/AbstractTrees/red_black_tree.py deleted file mode 100644 index 676f02a..0000000 --- a/datastax/Trees/AbstractTrees/red_black_tree.py +++ /dev/null @@ -1,234 +0,0 @@ -from __future__ import annotations - -import math -from itertools import chain -from typing import Any, Optional - -from datastax.Trees.AbstractTrees.binary_tree import ( - TreeNode, - BinaryTree, - _mangled, -) - -RED = 0 -BLACK = 1 - -black, red, grey = '232m', '196m', '237m' -fore, back, reset = '\x1B[38;5;', '\x1B[48;5;', '\x1b[0m' - - -def _node_builder(data: Optional[str], piece_width: int, n: int = 0) -> str: - value: str = data or '' - n = n or len(value) - 33 if value else 0 - - gap1 = int(math.ceil(piece_width / 2 - n / 2)) - gap2 = int(math.floor(piece_width / 2 - n / 2)) - return f"{' ' * gap1}{value}{' ' * gap2}" - - -def _format(color, data): - if color is BLACK: - return f"{fore}{red}{back}{black} {data} {back}{grey}" - return f"{fore}{black}{back}{red} {data} {back}{grey}" - - -class RedBlackNode(TreeNode): - def __init__(self, data: Any, - left: RedBlackNode = None, - right: RedBlackNode = None, - color: int = RED): - super().__init__(data, left, right) - self.parent: Optional[RedBlackNode] = None - self.color = color - - def __str__(self): - values = list( - map( - lambda node: "" if node is None else _format( - node.color, _mangled(node.data) - ), [self, self.left, self.right] - ) - ) - max_width = max(len(_mangled(data)) - 33 for data in values if data) - max_width += max_width % 2 # To make max_width even - padding = 4 - per_piece = 2 * (max_width + padding) - extra_line = f"{back}{grey}{' ' * (per_piece + 1)}{reset}\n" - - string_builder = ( - f"{extra_line}" - f"{back}{grey}{_node_builder(values[0], per_piece)} " - f"{reset}\n{back}{grey}" - ) - per_piece //= 2 - hpw = int(per_piece // 2 - 1) - if any(values[1:]): - if all(values[1:]): - part = f"{' ' * (hpw + 1)}┌{'─' * hpw}┴{'─' * hpw}┐" - string_builder += ( - f"{part}{' ' * (len(part) - per_piece - 1)}" - f"{reset}\n{back}{grey}" - ) - string_builder += _node_builder( - values[1], per_piece - ) + _node_builder( - values[2], per_piece - ) - elif values[1]: - part = f"{' ' * (hpw + 1)}┌{'─' * hpw}┘ {' ' * hpw}" - string_builder += ( - f"{part}{' ' * (len(part) - per_piece - 1)}" - f"{reset}\n{back}{grey}" - ) - string = _node_builder(values[1], per_piece) - string_builder += f"{string}{' ' * (len(string) - 33)}" - else: - part = f"{' ' * (per_piece - 1)} └{'─' * hpw}┐" - string_builder += ( - f"{part}{' ' * (len(part) - per_piece - 1)}" - f"{reset}\n{back}{grey}" - ) - string_builder += ( - f"{' ' * (per_piece - 1)} " - f"{_node_builder(values[2], per_piece)}" - ) - string_builder += f" {reset}\n{extra_line}" - return string_builder - - def preorder_print(self) -> str: - values = list( - map( - lambda node: "" if node is None else _format( - node.color, _mangled(node.data) - ), [self, self.left, self.right] - ) - ) - string_builder = f'\n{back}{grey}{values[0]} {reset}\n' - if any(values[1:]): - if all(values[1:]): - string_builder += ( - f"{back}{grey}├─▶ {values[1]} {reset}\n" - f"{back}{grey}└─▶ {values[2]} {reset}\n" - ) - else: - data = values[1] or values[2] - string_builder += f"{back}{grey}└─▶ {data} {reset}\n" - - return string_builder - - -class RedBlackTree(BinaryTree): - def insert(self, item: Any): - raise NotImplementedError - - @staticmethod - def _nodes_level_wise(root: RedBlackNode) -> list[list]: - level: list = [root] - nodes: int = 1 - levels: list[list] = [] - while nodes: - current_level: list[Optional[RedBlackNode]] = [] - next_level = [] - nodes = 0 - for node in level: - if not node: - current_level.append(None) - next_level += [None] * 2 - continue - current_level.append(node) - next_level += [node.left, node.right] - if node.left: - nodes += 1 - if node.right: - nodes += 1 - levels.append(current_level) - level = next_level - return levels - - @staticmethod - def _maximum_width(levels: list[list]) -> int: - max_width = 0 - for node in filter(bool, chain(*levels)): - data = _mangled(node.data) - max_width = max(max_width, len(data) + 4) - return max_width + 1 if max_width % 2 else max_width - - # Level order Traversal of Tree - def __str__(self): # noqa: C901 - # return super().__str__() - root = self.root - if not root: - return " NULL" - levels = self._nodes_level_wise(root) - max_width = self._maximum_width(levels) + 1 - padding = 4 - per_piece = len(levels[-1]) * max_width - extra_line = f"{back}{grey}{' ' * (per_piece + padding)}{reset}" - strings: list[str] = [extra_line] - for level in levels: - # printing the data first - data_string = f'{back}{grey}' - for node in level: - data = '' - if node: - data = _mangled(node.data) - data = _format(node.color, data) - data = _node_builder(data, per_piece) - data_string += data - per_piece //= 2 - data_string += f" {reset}" - - # printing the piece - piece_string = f'{back}{grey}' - per_node = per_piece // 2 - for node in level: - piece = ' ' * max_width - if node: - if node.left and node.right: - piece = ( - f"┌{'─' * (per_node - 1)}┴{'─' * (per_node - 1)}┐" - ) - elif node.left: - piece = ( - f" ┌{'─' * (per_node - 1)}┘{' ' * (per_node + 1)}" - ) - elif node.right: - piece = ( - f"{' ' * (per_node + 1)}└{'─' * (per_node - 1)}┐" - ) - piece = _node_builder(piece, per_piece * 2, len(piece)) - piece_string += piece - - piece_string += f" {reset}" - strings += [data_string, piece_string] - return '\n'.join(strings) - - # Pre Order Traversal of Tree - def preorder_print(self) -> None: - def string_builder(parent: Optional[RedBlackNode], - has_right_child: bool, - padding="", component="") -> None: - if not parent: - return - data = f'{back}{grey}' - padding += f'{back}{grey}' - if self._string is not None: - data += _format(parent.color, _mangled(parent.data)) - data += f" {reset}" - self._string += f"\n{padding}{component}{_mangled(data)}" - if parent is not root: - padding += "│ " if has_right_child else " " - left_pointer = "├─▶ " if parent.right else "└─▶ " - right_pointer = "└─▶ " - string_builder(parent.left, bool(parent.right), padding, - left_pointer) - string_builder(parent.right, False, padding, right_pointer) - - root = self.root - if not root: - self._string = "NULL" - print("NULL") - return - self._string = "" - string_builder(root, bool(root.right)) - print(self._string) diff --git a/datastax/Trees/binary_search_tree.py b/datastax/Trees/BinarySearchTree.py similarity index 80% rename from datastax/Trees/binary_search_tree.py rename to datastax/Trees/BinarySearchTree.py index 674e091..91c33dd 100644 --- a/datastax/Trees/binary_search_tree.py +++ b/datastax/Trees/BinarySearchTree.py @@ -1,26 +1,30 @@ -# Binary Search Tree Implementation -from __future__ import annotations - import warnings -from typing import Optional, Any +from typing import Any, Optional, Self, Sequence -from datastax.errors import ( +from datastax.Utils.Warnings import ( DuplicateNodeWarning, DeletionFromEmptyTreeWarning, NodeNotFoundWarning ) -from datastax.Trees.AbstractTrees.binary_tree import BinaryTree, TreeNode +from datastax.Nodes import TreeNode +from datastax.Trees.BinaryTree import BinaryTree class BinarySearchTree(BinaryTree): - def insert(self, data: Any) -> None: root = self.root if data is None: return result = self._place(root, data) if result: - self._root = result + self.set_root(result) + + def _construct(self, items: Optional[Sequence] = None) -> Self | None: + if not items or items[0] is None: + return None + for item in items: + self.insert(item) + return self def search(self, data: Any): """ @@ -57,9 +61,9 @@ def _place(self, parent, data) -> Optional[TreeNode]: if not parent: return TreeNode(data) elif parent.data < data: - parent.right = self._place(parent.right, data) + parent.set_right(self._place(parent.right, data)) elif parent.data > data: - parent.left = self._place(parent.left, data) + parent.set_left(self._place(parent.left, data)) else: warnings.warn( f"Insertion unsuccessful. Item '{data}' already exists " @@ -87,11 +91,11 @@ def _delete(self, root, item: Any): # Node with both children, replace with inorder_predecessor predecessor = self.inorder_predecessor(root) root.data = predecessor.data - root.left = self._delete(root.left, root.data) + root.set_left(self._delete(root.left, root.data)) elif item < root.data: - root.left = self._delete(root.left, item) + root.set_left(self._delete(root.left, item)) elif root.data < item: - root.right = self._delete(root.right, item) + root.set_right(self._delete(root.right, item)) return root def delete(self, data: Any = None) -> None: @@ -112,4 +116,4 @@ def delete(self, data: Any = None) -> None: f"data '{data}'", NodeNotFoundWarning ) return - self._root = self._delete(self.root, data) + self.set_root(self._delete(self.root, data)) diff --git a/datastax/Trees/binary_tree.py b/datastax/Trees/BinaryTree.py similarity index 70% rename from datastax/Trees/binary_tree.py rename to datastax/Trees/BinaryTree.py index 4087968..bd9dd9e 100644 --- a/datastax/Trees/binary_tree.py +++ b/datastax/Trees/BinaryTree.py @@ -1,29 +1,40 @@ -# Binary Tree Implementation -from __future__ import annotations - import warnings -from typing import Any, Optional - -from datastax.errors import ( - PathNotGivenError, - PathNotFoundError, +from typing import Any, Optional, Self, Sequence +from datastax.Utils.Exceptions import ( + PathNotGivenException, + PathNotFoundException, +) +from datastax.Utils.Warnings import ( PathAlreadyOccupiedWarning, NodeNotFoundWarning, DeletionFromEmptyTreeWarning, ) from datastax.Lists import Queue -from datastax.Trees.AbstractTrees import binary_tree -from datastax.Trees.AbstractTrees.binary_tree import TreeNode +from datastax.Nodes import TreeNode +from datastax.Trees.AbstractTrees import BinaryTree as AbstractTree + +class BinaryTree(AbstractTree): + def __init__(self, items: Optional[Sequence] = None, + root: Optional[TreeNode] = None): + self.set_root(root) + self._construct(items) + self._string: Optional[str] = None -class BinaryTree(binary_tree.BinaryTree): - def insert_path(self, data: Any, path: list[str] = None) -> None: + def set_root(self, root: TreeNode | None): + if root is None or isinstance(root, TreeNode): + self._root = root + return + raise TypeError("The 'root' parameter must be an " + "instance of TreeNode or its subclass.") + + def insert_path(self, data: Any, path: Optional[list[str]] = None) -> None: node = TreeNode(data) if not self._root: self._root = node return if not path: - raise PathNotGivenError(self) + raise PathNotGivenException(self) parent = self._root for direction in path[:-1]: # Reaching if direction == 'left' and parent.left: @@ -31,11 +42,11 @@ def insert_path(self, data: Any, path: list[str] = None) -> None: elif direction == 'right' and parent.right: parent = parent.right else: - raise PathNotFoundError(self) + raise PathNotFoundException(self) if path[-1] == 'right' and not parent.right: - parent.right = node + parent.set_right(node) elif path[-1] == 'left' and not parent.left: - parent.left = node + parent.set_left(node) else: occupied_node = parent.left if path[-1] == 'left' else parent.right warnings.warn("Insertion unsuccessful. Path already occupied by " @@ -43,28 +54,28 @@ def insert_path(self, data: Any, path: list[str] = None) -> None: PathAlreadyOccupiedWarning) # Helper function to construct tree by level order -> Array to tree - def _construct(self, array: list[Any] = None) -> Optional[BinaryTree]: - if not array or array[0] is None: + def _construct(self, items: Optional[Sequence] = None) -> Self | None: + if not items or items[0] is None: return None - queue = Queue(capacity=len(array)) + queue = Queue(capacity=len(items)) current = 0 root = self.root if not root: - root = TreeNode(array[0]) + root = TreeNode(items[0]) current = 1 queue.enqueue(root) - while not queue.is_empty() and current < len(array): + while not queue.is_empty() and current < len(items): node = queue.dequeue() - node.left = None if array[current] is None else TreeNode( - array[current]) + node.set_left(None if items[current] is None else TreeNode( + items[current])) if node.left: queue.enqueue(node.left) # Inserting Left Node current += 1 - if current >= len(array): + if current >= len(items): break - node.right = None if array[current] is None else TreeNode( - array[current]) + node.set_right(None if items[current] is None else TreeNode( + items[current])) if node.right: queue.enqueue(node.right) # Inserting Right Node current += 1 @@ -129,13 +140,13 @@ def delete_deepest(self) -> Optional[Any]: if parent: if parent.right: data = parent.right.data - parent.right = None + parent.set_right(None) else: data = parent.left.data - parent.left = None + parent.set_left(None) elif self._root: data = self._root.data - self._root = None + self.set_root(None) else: warnings.warn( "Deletion Unsuccessful. Can't delete from empty Tree", @@ -143,8 +154,7 @@ def delete_deepest(self) -> Optional[Any]: ) return data - def insert(self, item: Any): - + def insert(self, item: Any) -> None: temp = None if item is None else TreeNode(item) queue = Queue(capacity=len(self.array_repr)) if self.root: @@ -154,12 +164,12 @@ def insert(self, item: Any): if node.left: queue.enqueue(node.left) else: - node.left = temp + node.set_left(temp) return if node.right: queue.enqueue(node.right) else: - node.right = temp + node.set_right(temp) return else: self._root = temp diff --git a/datastax/Trees/expression_tree.py b/datastax/Trees/ExpressionTree.py similarity index 56% rename from datastax/Trees/expression_tree.py rename to datastax/Trees/ExpressionTree.py index a91f263..5ca4b40 100644 --- a/datastax/Trees/expression_tree.py +++ b/datastax/Trees/ExpressionTree.py @@ -1,44 +1,53 @@ -# Expression Tree Implementation -from __future__ import annotations +from typing import Any, Optional, Self, Sequence -from typing import Optional, Union, Any - -from datastax.Arrays import Stack -from datastax.errors import ( - UnmatchedBracketPairError, InvalidExpressionError, - UnderFlowError, OverFlowError +from datastax.Utils.Exceptions import ( + UnmatchedBracketPairException, + InvalidExpressionException, + UnderflowException, + OverflowException ) -from datastax.Trees.AbstractTrees.binary_tree import TreeNode, BinaryTree +from datastax.Arrays import Stack +from datastax.Nodes import TreeNode +from datastax.Trees.BinaryTree import BinaryTree class ExpressionTree(BinaryTree): - def __init__(self, infix_expression: Union[list, str] = None): - self.infix_expression = '' - self.postfix_expression = '' + _infix_expression = "" + _postfix_expression = "" + + @property + def infix_expression(self): + return self._infix_expression + + @property + def postfix_expression(self): + return self._postfix_expression + + def __init__(self, infix_expression: Optional[Sequence] = None): super().__init__(infix_expression) - def _construct(self, infix_expression: Union[list, str] = None - ) -> Optional[ExpressionTree]: + def _construct(self, infix_expression: Optional[Sequence] = None + ) -> Optional[Self]: if not infix_expression or infix_expression[0] is None: return None infix_expression = [*filter(lambda x: x is not None, infix_expression)] - self.infix_expression = ''.join(map(str, infix_expression)) - self.postfix_expression = self.infix_to_postfix() + self._set_infix(''.join(map(str, infix_expression))) + self._set_postfix(self.infix_to_postfix()) stack = Stack(capacity=len(infix_expression)) - for item in self.postfix_expression.split(): + for item in self._postfix_expression.split(): if self.is_operator(item): try: right, left = stack.pop(), stack.pop() node = TreeNode(item, left, right) - except UnderFlowError: - raise InvalidExpressionError(self) + except UnderflowException: + raise InvalidExpressionException(self) else: node = TreeNode(item) try: stack.push(node) - except OverFlowError: - raise InvalidExpressionError(self) - self._root = stack.pop() + except OverflowException: + raise InvalidExpressionException(self) + self.set_root(stack.pop()) return self @staticmethod @@ -47,22 +56,21 @@ def is_operator(character: str) -> bool: @staticmethod def precedence_of(operator: str) -> int: - if operator == '^': - return 1 - if operator in ('*', '/', '%'): - return 2 - if operator in ('+', '-'): - return 3 - return 4 - - def infix_to_postfix(self, infix_expression=''): + precedence = { + '^': 1, + '*': 2, '/': 2, '%': 2, + '+': 3, '-': 3 + } + return precedence.get(operator, 4) + + def infix_to_postfix(self, infix_expression: Optional[str] = '') -> str: if not infix_expression: - infix_expression = self.infix_expression + infix_expression = self._infix_expression if infix_expression.count('(') != infix_expression.count(')'): - raise UnmatchedBracketPairError(self, infix_expression) + raise UnmatchedBracketPairException(self, infix_expression) - postfix_expression: str = '' + postfix_expression = '' stack = Stack(capacity=len(infix_expression)) infix_expression += ')' stack.push('(') @@ -99,5 +107,14 @@ def infix_to_postfix(self, infix_expression=''): return postfix_expression + def _set_infix(self, infix_expression: str): + self._infix_expression = infix_expression + + def _set_postfix(self, postfix_expression: str): + self._postfix_expression = postfix_expression + def insert(self, item: Any): raise NotImplementedError + + def insert_path(self, data: Any, path: Optional[list[str]] = None) -> None: + raise NotImplementedError diff --git a/datastax/Trees/fibonacci_tree.py b/datastax/Trees/FibonacciTree.py similarity index 53% rename from datastax/Trees/fibonacci_tree.py rename to datastax/Trees/FibonacciTree.py index ea358de..6ccb2f9 100644 --- a/datastax/Trees/fibonacci_tree.py +++ b/datastax/Trees/FibonacciTree.py @@ -1,43 +1,48 @@ -# Fibonacci Tree Implementation -from __future__ import annotations - -from typing import Optional, Any - -from datastax.Trees.AbstractTrees.binary_tree import BinaryTree, TreeNode +from typing import Any, Optional, Self, Sequence +from datastax.Trees.BinaryTree import BinaryTree +from datastax.Nodes import TreeNode class FibonacciTree(BinaryTree): - def __init__(self, nth: int = None): - self._n = nth + _terms: Optional[int] = None + _series: Optional[list] = None + + def __init__(self, terms: int): + self._terms = terms self._series = None - super().__init__(nth) + super().__init__([terms]) + + @property + def terms(self): + return self._terms @property def series(self): self._series = [] first, second = 0, 1 - if self._n == 0: + if self._terms == 0: self._series.append(first) - elif self._n == 1: + elif self._terms == 1: self._series.append(first) self._series.append(second) else: count = 0 - while count < self._n + 1: + while count < self._terms + 1: self._series.append(first) first, second = second, first + second count += 1 return self._series - def _construct(self, n: int = None) -> Optional[FibonacciTree]: - if n is None or n < 0: + def _construct(self, items: Optional[Sequence] = None) -> Optional[Self]: + print(not items, items) + if not items or items[0] <= 0: return None - + n = items[0] self._root = self._fibonacci(n) return self def _fibonacci(self, n: int, - memo: dict[int, TreeNode] = None) -> TreeNode: + memo: Optional[dict[int, TreeNode]] = None) -> TreeNode: if memo is None: memo = {0: TreeNode(0), 1: TreeNode(1)} if n in memo: @@ -49,3 +54,6 @@ def _fibonacci(self, n: int, def insert(self, item: Any): raise NotImplementedError + + def insert_path(self, data: Any, path: Optional[list[str]] = None) -> None: + raise NotImplementedError diff --git a/datastax/Trees/heap_tree.py b/datastax/Trees/HeapTree.py similarity index 66% rename from datastax/Trees/heap_tree.py rename to datastax/Trees/HeapTree.py index 4bccef6..9b5e4ff 100644 --- a/datastax/Trees/heap_tree.py +++ b/datastax/Trees/HeapTree.py @@ -1,42 +1,42 @@ -# Heap Tree Implementation -from __future__ import annotations - import warnings -from typing import Optional, Any +from typing import Any, Optional, Self, Sequence -from datastax.errors import DeletionFromEmptyTreeWarning -from datastax.Trees.AbstractTrees.binary_tree import BinaryTree, TreeNode +from datastax.Utils.Warnings import DeletionFromEmptyTreeWarning +from datastax.Trees.BinaryTree import BinaryTree +from datastax.Nodes import HeapNode -class HeapNode(TreeNode): - def __init__(self, data: Any, - left: HeapNode = None, - right: HeapNode = None): - super().__init__(data, left, right) - self.parent: Optional[HeapNode] = None - self.prev_leaf: Optional[HeapNode] = None +class HeapTree(BinaryTree): + _leaf: Optional[HeapNode] + def __init__(self, items: Optional[list] = None, + root: Optional[HeapNode] = None): + self.set_root(root) + self.set_leaf(root) + super().__init__(items, root) -class HeapTree(BinaryTree): - def __init__(self, array: list[Any] = None, root: HeapNode = None): - self._root: Optional[HeapNode] = root - self._leaf: Optional[HeapNode] = root - super().__init__(array, root) + @property + def leaf(self): + return self._leaf + + def set_leaf(self, leaf: HeapNode | None): + if leaf is None or isinstance(leaf, HeapNode): + self._leaf = leaf + return + raise TypeError("The 'leaf' parameter must be an " + "instance of HeapNode or its subclass.") - def _construct(self, array: list[Any] = None) -> Optional[HeapTree]: - if not array or array[0] is None: + def _construct(self, items: Optional[Sequence] = None) -> Self | None: + if not items or items[0] is None: return None - for item in array: + + for item in items: try: self.heappush(item) except TypeError as error: raise error return self - @property - def leaf(self): - return self._leaf - # Function to push an element inside a tree def heappush(self, data: Any) -> None: root = self.root @@ -44,20 +44,21 @@ def heappush(self, data: Any) -> None: return node = HeapNode(data) if root is None: # Heap Tree is Empty - self._root = self._leaf = node + self.set_root(node) + self.set_leaf(node) # Heap tree has nodes. So inserting new node # in the left of leftmost leaf node elif self.leaf and self.leaf.left is None: - self.leaf.left = node - node.parent = self.leaf + self.leaf.set_left(node) + node.set_parent(self.leaf) else: if not self.leaf: return - self.leaf.right = node + self.leaf.set_right(node) previous_leaf = self.leaf - node.parent = self.leaf + node.set_parent(previous_leaf) self._update_leaf(self.leaf) - self.leaf.prev_leaf = previous_leaf + self.leaf.set_prev_leaf(previous_leaf) self._heapify(node) # Private function to convert a subtree to heap @@ -67,17 +68,17 @@ def _heapify(self, node: HeapNode) -> None: self._heapify(node.parent) # Private Helper method of heappush function to - # update rightmost node in deepest level + # update rightmost node in the deepest level def _update_leaf(self, node: HeapNode) -> None: # reach extreme left of next level if current level is full if node.parent is None: - self._leaf = node + self.set_leaf(node) elif node.parent.left is node: - self._leaf = node.parent.right + self.set_leaf(node.parent.right) elif node.parent.right is node: self._update_leaf(node.parent) while self.leaf and self.leaf.left: - self._leaf = self.leaf.left + self.set_leaf(self.leaf.left) # Function to pop the largest element in the tree def heappop(self) -> Optional[Any]: @@ -90,19 +91,19 @@ def heappop(self) -> Optional[Any]: deleted_data = self.root.data if self.root is self.leaf and not any( [self.leaf.left, self.leaf.right]): - self._root = self._leaf = None - + self.set_root(None) + self.set_leaf(None) else: if self.leaf.right and self.root: self.root.data = self.leaf.right.data - self.leaf.right = None + self.leaf.set_right(None) self._shift_up(self.root) elif self.leaf.left and self.root: self.root.data = self.leaf.left.data - self.leaf.left = None + self.leaf.set_left(None) self._shift_up(self.root) else: # We have reached the end of a level - self._leaf = self.leaf.prev_leaf + self.set_leaf(self.leaf.prev_leaf) return self.heappop() return deleted_data diff --git a/datastax/Trees/huffman_tree.py b/datastax/Trees/HuffmanTree.py similarity index 74% rename from datastax/Trees/huffman_tree.py rename to datastax/Trees/HuffmanTree.py index 082ccf7..19e05f2 100644 --- a/datastax/Trees/huffman_tree.py +++ b/datastax/Trees/HuffmanTree.py @@ -1,26 +1,20 @@ -# Implementation of Variable size Huffman Coding Tree -from __future__ import annotations - from collections import Counter -from typing import Any, Optional, Union - +from typing import Any, Optional, Self, Sequence from datastax.Arrays import PriorityQueue -from datastax.Trees.AbstractTrees import huffman_tree -from datastax.Trees.AbstractTrees.huffman_tree import HuffmanNode +from datastax.Nodes import HuffmanNode +from datastax.Tables import HuffmanTable +from datastax.Trees.BinaryTree import BinaryTree +from datastax.Trees.AbstractTrees import HuffmanTree as AbstractTree -class HuffmanTable(huffman_tree.HuffmanTable): - def _calculate_size(self): - size = 0 - for char, huff_code in self.data.items(): - size += ord(char).bit_length() + len(huff_code) - self._size = size +class HuffmanTree(BinaryTree, AbstractTree): + def __init__(self, data: Optional[Sequence] = None): + self.data = data + super().__init__(data) - -class HuffmanTree(huffman_tree.HuffmanTree): - def _construct(self, data: Union[list[str], str] = None - ) -> Optional[HuffmanTree]: - if not data or data[0] is None: + def _construct(self, + items: Optional[Sequence] = None) -> Self | None: + if not items or items[0] is None: return None def comparator(n1: HuffmanNode, n2: HuffmanNode) -> HuffmanNode: @@ -30,8 +24,8 @@ def comparator(n1: HuffmanNode, n2: HuffmanNode) -> HuffmanNode: nodes = ( HuffmanNode( - _data, None, None, frequency - ) for _data, frequency in Counter(data).items() + data, None, None, frequency + ) for data, frequency in Counter(items).items() ) p_queue = PriorityQueue(capacity=None, custom_comparator=comparator) for node in nodes: @@ -43,13 +37,12 @@ def comparator(n1: HuffmanNode, n2: HuffmanNode) -> HuffmanNode: root_freq = sum((node1.frequency, node2.frequency)) root = HuffmanNode(None, node1, node2, root_freq) p_queue.enqueue(root) - - self._root = p_queue.dequeue() + self.set_root(p_queue.dequeue()) self._calculate_huffman_code() self._create_huffman_table() return self - def huffman_code_of(self, character: str) -> Optional[str]: + def huffman_code_of(self, character: str) -> str | None: def find(node: HuffmanNode, path=None): if not node: return None @@ -71,17 +64,17 @@ def find(node: HuffmanNode, path=None): # Private method to build data dictionary for HuffmanTable def _create_huffman_table(self): items = {} - for item in self._data: + for item in self.data: items[item] = self.huffman_code_of(item) - self._table = HuffmanTable(items, Counter(self._data)) + self._table = HuffmanTable(items, Counter(self.data)) def _calculate_huffman_code(self): self._huffman_code = ''.join( - self.huffman_code_of(character) for character in self._data + self.huffman_code_of(character) for character in self.data ) pass - def size_calculator(self) -> Optional[tuple[int, int]]: + def size_calculator(self) -> tuple[int, int] | None: """ Calculates the actual encoding size and total huffman encoding size with table included @@ -89,21 +82,21 @@ def size_calculator(self) -> Optional[tuple[int, int]]: if not self.root or not self.huffman_table: return None fixed_encoding = huffman_encoding = 0 - frequency = self.huffman_table.frequency + frequencies = self.huffman_table.frequencies for char, huff_code in self.huffman_table.data.items(): # Converting item to ascii finding bit_length and multiplying # it with frequency - total_bit_length = ord(char).bit_length() * frequency[char] + total_bit_length = ord(char).bit_length() * frequencies[char] fixed_encoding += total_bit_length # Adding to fixed_encoding # Already in Binary so no conversion to ascii - total_bit_length = len(huff_code) * frequency[char] + total_bit_length = len(huff_code) * frequencies[char] huffman_encoding += total_bit_length # Adding to huffman_encoding return fixed_encoding, huffman_encoding + self.huffman_table.size - def compression_ratio(self) -> Optional[str]: + def compression_ratio(self) -> str | None: if not self.root: return None result = self.size_calculator() @@ -113,7 +106,7 @@ def compression_ratio(self) -> Optional[str]: compression_ratio = huffman_encoding / fixed_encoding return f"{compression_ratio :.2%}" - def space_saved(self) -> Optional[str]: + def space_saved(self) -> str | None: if not self.root: return None result = self.size_calculator() diff --git a/datastax/Trees/min_heap_tree.py b/datastax/Trees/MinHeapTree.py similarity index 84% rename from datastax/Trees/min_heap_tree.py rename to datastax/Trees/MinHeapTree.py index be29dcd..3d14baa 100644 --- a/datastax/Trees/min_heap_tree.py +++ b/datastax/Trees/MinHeapTree.py @@ -1,7 +1,5 @@ -# Min Heap Tree Implementation -from __future__ import annotations - -from datastax.Trees.heap_tree import HeapTree, HeapNode +from datastax.Trees.HeapTree import HeapTree +from datastax.Nodes import HeapNode class MinHeapTree(HeapTree): diff --git a/datastax/Trees/min_segment_tree.py b/datastax/Trees/MinSegmentTree.py similarity index 72% rename from datastax/Trees/min_segment_tree.py rename to datastax/Trees/MinSegmentTree.py index a346527..a5dea53 100644 --- a/datastax/Trees/min_segment_tree.py +++ b/datastax/Trees/MinSegmentTree.py @@ -1,45 +1,45 @@ -# Min Segment Tree implementation -from __future__ import annotations - from sys import maxsize -from typing import Any, Optional - -from datastax.Trees.AbstractTrees.segment_tree import SegmentTree, SegmentNode +from typing import Optional, Self, Sequence +from datastax.Trees.SegmentTree import SegmentTree +from datastax.Nodes import SegmentNode class MinSegmentTree(SegmentTree): - def __init__(self, array=None, root=None): - self._lazy_tree = SegmentTree() - super().__init__(array, root) + _lazy_tree = SegmentTree() - def insert(self, item: Any): - raise NotImplementedError + def __init__(self, item: Optional[Sequence] = None, + root: Optional[SegmentNode] = None): + super().__init__(item, root) @property def lazy_tree(self): return self._lazy_tree - def _construct(self, array: list[int] = None) -> Optional[MinSegmentTree]: + def _construct(self, array: Optional[Sequence] = None) -> Self | None: if not array or array[0] is None: return None - def build(left: int, right: int) -> tuple[SegmentNode, ...]: + def build(left: int, right: int) -> tuple[SegmentNode, SegmentNode]: node, lazy_node = SegmentNode(None), SegmentNode(maxsize) # Leaf Node if array and left == right: node.left_index, node.right_index = left, right + lazy_node.left_index, lazy_node.right_index = left, right node.data = array[left] return node, lazy_node # Intermediate Node mid = (left + right) // 2 - node.left, lazy_node.left = build(left, mid) - node.right, lazy_node.right = build(mid + 1, right) + left_node, lazy_left_node = build(left, mid) + right_node, lazy_right_node = build(mid + 1, right) + node.set_left(left_node), lazy_node.set_left(lazy_left_node) + node.set_right(right_node), lazy_node.set_right(lazy_right_node) node.left_index = lazy_node.left_index = node.left.left_index node.right_index = lazy_node.right_index = node.right.right_index node.data = min(node.left.data, node.right.data) return node, lazy_node - self._root, self._lazy_tree._root = build(0, len(array) - 1) + root, lazy_root = build(0, len(array) - 1) + self.set_root(root), self.lazy_tree.set_root(lazy_root) return self @staticmethod @@ -53,12 +53,12 @@ def _perform_lazy_update(root: SegmentNode, lazy_node: SegmentNode, # No longer a lazy node lazy_node.data = maxsize - def get_min(self, left: int, right: int, - root: SegmentNode = None, - lazy_node: SegmentNode = None) -> int: - if not root: - root = self.root - lazy_node = self.lazy_tree.root + def get_min(self, left: int, right: int) -> int: + return self.get_range(left, right, self.root, self.lazy_tree.root) + + def get_range(self, left: int, right: int, + root: SegmentNode | None, + lazy_node: SegmentNode | None) -> int: if not root or not lazy_node: return maxsize @@ -76,25 +76,10 @@ def get_min(self, left: int, right: int, return root.data return min( - self.get_min(left, right, root.left, lazy_node.left), - self.get_min(left, right, root.right, lazy_node.right) + self.get_range(left, right, root.left, lazy_node.left), + self.get_range(left, right, root.right, lazy_node.right) ) - def update_at_index(self, index: int, data: int): - if not self.root: - return None - - def update(node: SegmentNode, new): - if index == node.left_index == node.right_index: - node.data = new - mid = (node.left_index + node.right_index) // 2 - update_node = node.left if index <= mid else node.right - update(update_node, new) - node.data = min(node.left.data if node.left else node.data, - node.right.data if node.right else node.data) - - update(self.root, data) - def update_at_range(self, left: int, right: int, data: int) -> None: if not self.root: return None @@ -126,6 +111,21 @@ def update(node: SegmentNode, lazy_node: SegmentNode) -> None: update(self.root, self.lazy_tree.root) + def update_at_index(self, index: int, data: int) -> None: + if not self.root: + return None + + def update(node: SegmentNode, new: int): + if index == node.left_index == node.right_index: + node.data = new + mid = (node.left_index + node.right_index) // 2 + update_node = node.left if index <= mid else node.right + update(update_node, new) + node.data = min(node.left.data if node.left else node.data, + node.right.data if node.right else node.data) + + update(self.root, data) + if __name__ == '__main__': tree = MinSegmentTree([2, 3, 1, 4, 2, 5, 2, 3, 1, 5]) @@ -138,5 +138,3 @@ def update(node: SegmentNode, lazy_node: SegmentNode) -> None: print(tree) print(tree.lazy_tree) print(tree.segment_array) - print(tree) - print(tree.lazy_tree) diff --git a/datastax/Trees/red_black_tree.py b/datastax/Trees/RedBlackTree.py similarity index 65% rename from datastax/Trees/red_black_tree.py rename to datastax/Trees/RedBlackTree.py index dedf776..5fecb0e 100644 --- a/datastax/Trees/red_black_tree.py +++ b/datastax/Trees/RedBlackTree.py @@ -1,22 +1,15 @@ -# Implementation of Variable size Huffman Coding Tree -from __future__ import annotations - import warnings from typing import Optional, Any +from datastax.Utils.Warnings import DuplicateNodeWarning +from datastax.Utils import ColorCodes +from datastax.Nodes import RedBlackNode +from datastax.Trees.BinarySearchTree import BinarySearchTree +from datastax.Trees.AbstractTrees import RedBlackTree as AbstractTree -from datastax.errors import ( - DuplicateNodeWarning -) -from datastax.Trees.binary_search_tree import BinarySearchTree -from datastax.Trees.AbstractTrees import red_black_tree -from datastax.Trees.AbstractTrees.red_black_tree import ( - RedBlackNode, - BLACK, RED -) +RED, BLACK = ColorCodes.RED, ColorCodes.BLACK -class RedBlackTree(BinarySearchTree, - red_black_tree.RedBlackTree): +class RedBlackTree(BinarySearchTree, AbstractTree): # Private helper function for inserting def _place(self, parent: Optional[RedBlackNode], @@ -37,15 +30,15 @@ def _place(self, ) return None - node.parent = parent + node.set_parent(parent) # Node to be added is root node if not parent: - node.color = BLACK + node.set_color(BLACK) return node if parent.data > node.data: - parent.left = node + parent.set_left(node) else: - parent.right = node + parent.set_right(node) self._post_place(node) return self.root @@ -60,15 +53,16 @@ def sibling_of(node: Optional[RedBlackNode]): def _post_place(self, node: Optional[RedBlackNode]): if not node or node is self.root: return - # * Resolve Red Red Conflict + # * Resolve Red-Red Conflict parent = node.parent if parent and parent.color is RED: sibling = self.sibling_of(parent) # CASE 1: Recolor and move up to see if more work required. if sibling and sibling.color is RED: - parent.color = sibling.color = BLACK + parent.set_color(BLACK) + sibling.set_color(BLACK) if parent.parent and parent.parent is not self.root: - parent.parent.color = RED + parent.parent.set_color(RED) self._post_place(parent.parent) # CASE 2: Color is black so restructuring (rotations) and # recoloring both are required. @@ -82,8 +76,8 @@ def _post_place(self, node: Optional[RedBlackNode]): # node is Right and parent is Left Child of G.Parent parent = self._left_rotate(parent) if parent and parent.parent: - parent.color = BLACK - parent.parent.color = RED + parent.set_color(BLACK) + parent.parent.set_color(RED) self._right_rotate(parent.parent) # CASE B: Parent is right child else: @@ -94,66 +88,66 @@ def _post_place(self, node: Optional[RedBlackNode]): # node is Left and parent is Right Child of G.Parent parent = self._right_rotate(parent) if parent and parent.parent: - parent.color = BLACK - parent.parent.color = RED + parent.set_color(BLACK) + parent.parent.set_color(RED) self._left_rotate(parent.parent) # Private helper method of balance function to perform RR rotation - def _right_rotate(self, node: RedBlackNode) -> Optional[RedBlackNode]: + def _right_rotate(self, node: RedBlackNode) -> RedBlackNode: left = node.left if not left: return left - left.parent = node.parent + left.set_parent(node.parent) - node.left = left.right + node.set_left(left.right) if node.left: - node.left.parent = node - left.right = node - node.parent = left + node.left.set_parent(node) + left.set_right(node) + node.set_parent(left) if left.parent: if node is left.parent.left: - left.parent.left = left + left.parent.set_left(left) else: - left.parent.right = left + left.parent.set_right(left) else: - self._root = left + self.set_root(left) return left # Private helper method of balance function to perform LL rotation - def _left_rotate(self, node: RedBlackNode) -> Optional[RedBlackNode]: + def _left_rotate(self, node: RedBlackNode) -> RedBlackNode: right = node.right if not right: return right - right.parent = node.parent + right.set_parent(node.parent) - node.right = right.left + node.set_right(right.left) if node.right: - node.right.parent = node + node.right.set_parent(node) - right.left = node - node.parent = right + right.set_left(node) + node.set_parent(right) if right.parent: if node is right.parent.left: - right.parent.left = right + right.parent.set_left(right) else: - right.parent.right = right + right.parent.set_right(right) else: - self._root = right + self.set_root(right) return right # Private helper method for delete to perform exchange of data between node def _transplant(self, node: RedBlackNode, new_node: Optional[RedBlackNode]) -> None: if not node.parent: - self._root = new_node + self.set_root(new_node) elif node is node.parent.left: - node.parent.left = new_node + node.parent.set_left(new_node) else: - node.parent.right = new_node + node.parent.set_right(new_node) if new_node: - new_node.parent = node.parent + new_node.set_parent(node.parent) def _delete(self, root, item: Any): node = self.search(item) @@ -165,17 +159,17 @@ def _delete(self, root, item: Any): color = predecessor.color pull_up = predecessor.left if predecessor.parent is node and pull_up: - pull_up.parent = predecessor + pull_up.set_parent(predecessor) else: self._transplant(predecessor, pull_up) - predecessor.left = node.left + predecessor.set_left(node.left) if node.left: - node.left.parent = predecessor + node.left.set_parent(predecessor) self._transplant(node, predecessor) - predecessor.right = node.right + predecessor.set_right(node.right) if node.right: - node.right.parent = predecessor - predecessor.color = node.color + node.right.set_parent(predecessor) + predecessor.set_color(node.color) else: pull_up = node.left if node.left else node.right self._transplant(node, pull_up) @@ -185,55 +179,56 @@ def _delete(self, root, item: Any): return self.root - def _resolve_left_black_conflict(self, node): + def _resolve_left_black_conflict(self, node: RedBlackNode) -> RedBlackNode: parent = node.parent sibling = parent.right - if sibling.color == RED: - sibling.color = BLACK - parent.color = RED + if sibling.color is RED: + sibling.set_color(BLACK) + parent.set_color(RED) self._left_rotate(parent) sibling = node.parent.right - if sibling.left.color == BLACK and sibling.right.color == BLACK: - sibling.color = RED + if sibling.left.color is BLACK and sibling.right.color is BLACK: + sibling.set_color(RED) node = parent else: - if sibling.right.color == BLACK: - sibling.left.color = BLACK - sibling.color = RED + if sibling.right.color is BLACK: + sibling.left.set_color(BLACK) + sibling.set_color(RED) self._right_rotate(sibling) sibling = parent.right - sibling.color = parent.color - parent.color = BLACK - sibling.right.color = BLACK + sibling.set_color(parent.color) + parent.set_color(BLACK) + sibling.right.set_color(BLACK) self._left_rotate(parent) node = self.root return node - def _resolve_right_black_conflict(self, node): + def _resolve_right_black_conflict(self, + node: RedBlackNode) -> RedBlackNode: parent = node.parent sibling = parent.left if sibling.color is RED: - sibling.color = BLACK - parent.color = RED + sibling.set_color(BLACK) + parent.set_color(RED) self._right_rotate(parent) sibling = parent.left if sibling.right.color is sibling.left.color is BLACK: - sibling.color = RED + sibling.set_color(RED) node = parent else: if sibling.left.color is BLACK: - sibling.right.color = BLACK - sibling.color = RED + sibling.right.set_color(BLACK) + sibling.set_color(RED) self._left_rotate(sibling) sibling = parent.left - sibling.color = parent.color - parent.color = BLACK - sibling.left.color = BLACK + sibling.set_color(parent.color) + parent.set_color(BLACK) + sibling.left.set_color(BLACK) self._right_rotate(parent) node = self.root return node @@ -249,4 +244,4 @@ def _post_delete(self, node: Optional[RedBlackNode]): else: node = self._resolve_right_black_conflict(node) if node: - node.color = BLACK + node.set_color(BLACK) diff --git a/datastax/Trees/SegmentTree.py b/datastax/Trees/SegmentTree.py new file mode 100644 index 0000000..2acef9a --- /dev/null +++ b/datastax/Trees/SegmentTree.py @@ -0,0 +1,40 @@ +from typing import Any, Optional +from datastax.Trees.BinaryTree import BinaryTree +from datastax.Nodes import SegmentNode +from datastax.Trees.AbstractTrees import SegmentTree as AbstractTree + + +class SegmentTree(BinaryTree, AbstractTree): + _segment_array = [] + + def update_at_range(self, left: int, right: int, data: int) -> None: + raise NotImplementedError + + def update_at_index(self, index: int, data: int) -> None: + raise NotImplementedError + + def get_range(self, left: int, right: int, + root: SegmentNode | None, + lazy_node: SegmentNode | None): + raise NotImplementedError + + def insert(self, item: Any): + raise NotImplementedError + + def delete(self, data: Any = None) -> Optional[Any]: + raise NotImplementedError + + def insert_path(self, data: Any, path: Optional[list[str]] = None) -> None: + raise NotImplementedError + + def _traverse_leafs(self, node: SegmentNode | None) -> None: + if not node: + return None + if not any((node.left, node.right)): + return self._segment_array.append(node.data) + # if left child is found, check for leaf node recursively + if node.left: + self._traverse_leafs(node.left) + # if right child is found, check for leaf node recursively + if node.right: + self._traverse_leafs(node.right) diff --git a/datastax/Trees/splay_tree.py b/datastax/Trees/SplayTree.py similarity index 80% rename from datastax/Trees/splay_tree.py rename to datastax/Trees/SplayTree.py index da2f8df..a1500bb 100644 --- a/datastax/Trees/splay_tree.py +++ b/datastax/Trees/SplayTree.py @@ -1,23 +1,14 @@ -# Splay Tree Implementation -from __future__ import annotations - import warnings from typing import Any, Optional -from datastax.errors import ( +from datastax.Utils.Warnings import ( DuplicateNodeWarning, DeletionFromEmptyTreeWarning, NodeNotFoundWarning ) -from datastax.Trees.binary_search_tree import BinarySearchTree, TreeNode - -class SplayNode(TreeNode): - def __init__(self, data: Any, - left: SplayNode = None, - right: SplayNode = None) -> None: - super().__init__(data, left, right) - self.parent: Optional[SplayNode] = None +from datastax.Trees.BinarySearchTree import BinarySearchTree +from datastax.Nodes import SplayNode class SplayTree(BinarySearchTree): @@ -41,14 +32,14 @@ def _place(self, parent: Optional[SplayNode], data) -> Optional[SplayNode]: if parent: self._splay(parent) return None - node.parent = parent + node.set_parent(parent) # Node to be added is root node if not parent: return node if parent.data > node.data: - parent.left = node + parent.set_left(node) else: - parent.right = node + parent.set_right(node) self._splay(node) return self.root @@ -94,21 +85,21 @@ def _zig_rotate(self, node: SplayNode) -> Optional[SplayNode]: left = node.left if not left: return left - left.parent = node.parent + left.set_parent(node.parent) - node.left = left.right + node.set_left(left.right) if node.left: - node.left.parent = node - left.right = node - node.parent = left + node.left.set_parent(node) + left.set_right(node) + node.set_parent(left) if left.parent: if node is left.parent.left: - left.parent.left = left + left.parent.set_left(left) else: - left.parent.right = left + left.parent.set_right(left) else: - self._root = left + self.set_root(left) return left # Private helper method of balance function to perform LL rotation @@ -116,22 +107,22 @@ def _zag_rotate(self, node: SplayNode) -> Optional[SplayNode]: right = node.right if not right: return right - right.parent = node.parent + right.set_parent(node.parent) - node.right = right.left + node.set_right(right.left) if node.right: - node.right.parent = node + node.right.set_parent(node) - right.left = node - node.parent = right + right.set_left(node) + node.set_parent(right) if right.parent: if node is right.parent.left: - right.parent.left = right + right.parent.set_left(right) else: - right.parent.right = right + right.parent.set_right(right) else: - self._root = right + self.set_root(right) return right def delete(self, data: Any = None) -> None: @@ -141,7 +132,7 @@ def delete(self, data: Any = None) -> None: DeletionFromEmptyTreeWarning ) return - self._root = self._delete(self.root, data) + self.set_root(self._delete(self.root, data)) def _delete(self, root, item: Any): node = self.search(item) @@ -152,20 +143,20 @@ def _delete(self, root, item: Any): # First completing leftSubTree left_tree = SplayTree(None, self.root.left) if left_tree.root: - left_tree.root.parent = None + left_tree.root.set_parent(None) right_tree = SplayTree(None, self.root.right) if right_tree.root: - right_tree.root.parent = None + right_tree.root.set_parent(None) if left_tree.root: # Finding the maximum element in left subtree predecessor = self.inorder_predecessor(self.root) if predecessor: left_tree._splay(predecessor) - left_tree.root.right = right_tree.root + left_tree.root.set_right(right_tree.root) if right_tree.root: - right_tree.root.parent = left_tree.root + right_tree.root.set_parent(left_tree.root) self._root = left_tree.root else: self._root = right_tree.root diff --git a/datastax/Trees/sum_segment_tree.py b/datastax/Trees/SumSegmentTree.py similarity index 69% rename from datastax/Trees/sum_segment_tree.py rename to datastax/Trees/SumSegmentTree.py index 41b8189..a044326 100644 --- a/datastax/Trees/sum_segment_tree.py +++ b/datastax/Trees/SumSegmentTree.py @@ -1,28 +1,24 @@ -# Sum Segment Tree implementation -from __future__ import annotations - -from typing import Any, Optional - -from datastax.Trees.AbstractTrees.segment_tree import SegmentTree, SegmentNode +from typing import Optional, Self, Sequence +from datastax.Trees.SegmentTree import SegmentTree +from datastax.Nodes import SegmentNode class SumSegmentTree(SegmentTree): - def __init__(self, array=None, root=None): - self._lazy_tree = SegmentTree() - super().__init__(array, root) + _lazy_tree = SegmentTree() - def insert(self, item: Any): - raise NotImplementedError + def __init__(self, items: Optional[Sequence] = None, + root: Optional[SegmentNode] = None): + super().__init__(items, root) @property def lazy_tree(self): return self._lazy_tree - def _construct(self, array: list[int] = None) -> Optional[SumSegmentTree]: + def _construct(self, array: Optional[Sequence] = None) -> Self | None: if not array or array[0] is None: return None - def build(left: int, right: int) -> tuple[SegmentNode, ...]: + def build(left: int, right: int) -> tuple[SegmentNode, SegmentNode]: node, lazy_node = SegmentNode(None), SegmentNode(0) # Leaf Node if array and left == right: @@ -32,14 +28,17 @@ def build(left: int, right: int) -> tuple[SegmentNode, ...]: return node, lazy_node # Intermediate Node mid = (left + right) // 2 - node.left, lazy_node.left = build(left, mid) - node.right, lazy_node.right = build(mid + 1, right) + left_node, lazy_left_node = build(left, mid) + right_node, lazy_right_node = build(mid + 1, right) + node.set_left(left_node), lazy_node.set_left(lazy_left_node) + node.set_right(right_node), lazy_node.set_right(lazy_right_node) node.left_index = lazy_node.left_index = node.left.left_index node.right_index = lazy_node.right_index = node.right.right_index node.data = sum((node.left.data, node.right.data)) return node, lazy_node - self._root, self._lazy_tree._root = build(0, len(array) - 1) + root, lazy_root = build(0, len(array) - 1) + self.set_root(root), self.lazy_tree.set_root(lazy_root) return self @staticmethod @@ -54,12 +53,12 @@ def _perform_lazy_update(root: SegmentNode, lazy_node: SegmentNode, # No longer a lazy node lazy_node.data = 0 - def get_sum(self, left: int, right: int, - root: SegmentNode = None, - lazy_node: SegmentNode = None) -> int: - if not root: - root = self.root - lazy_node = self.lazy_tree.root + def get_sum(self, left: int, right: int) -> int: + return self.get_range(left, right, self.root, self.lazy_tree.root) + + def get_range(self, left: int, right: int, + root: SegmentNode | None, + lazy_node: SegmentNode | None) -> int: if not root or not lazy_node: return 0 @@ -77,8 +76,8 @@ def get_sum(self, left: int, right: int, return root.data return sum(( - self.get_sum(left, right, root.left, lazy_node.left), - self.get_sum(left, right, root.right, lazy_node.right) + self.get_range(left, right, root.left, lazy_node.left), + self.get_range(left, right, root.right, lazy_node.right) )) def update_at_range(self, left: int, right: int, data: int) -> None: @@ -101,8 +100,8 @@ def update(node: SegmentNode, lazy_node: SegmentNode) -> None: node.data += data * nodes # Mark its children lazy, no need to propagate further if start != end: - lazy_node.left.data += data - lazy_node.right.data += data + lazy_node.left.data += node.data + lazy_node.right.data += node.data return # location is partially in bounds @@ -128,18 +127,3 @@ def update(node: SegmentNode, new: int): return difference update(self.root, data) - - -if __name__ == '__main__': - tree = SumSegmentTree([2, 3, 1, 4, 2, 5, 2, 3, 1, 5]) - # tree = SumSegmentTree() - print(tree) - tree.update_at_range(3, 9, +5) - print(tree) - tree.update_at_range(5, 8, +3) - print(tree.get_sum(3, 5)) - print(tree) - print(tree.lazy_tree) - print(tree.segment_array) - # print(tree) - # print(tree.lazy_tree) diff --git a/datastax/Trees/ThreadedBinaryTree.py b/datastax/Trees/ThreadedBinaryTree.py new file mode 100644 index 0000000..e31a428 --- /dev/null +++ b/datastax/Trees/ThreadedBinaryTree.py @@ -0,0 +1,164 @@ +import warnings +from typing import Any, Optional, Self, Sequence +from datastax.Utils.Warnings import DuplicateNodeWarning +from datastax.Lists import Queue +from datastax.Trees.BinaryTree import BinaryTree +from datastax.Trees.AbstractTrees import ThreadedBinaryTree as AbstractTree +from datastax.Nodes import ThreadedNode + + +class ThreadedBinaryTree(BinaryTree, AbstractTree): + dummy_node = ThreadedNode(None) + tail: Optional[ThreadedNode] = None + head: Optional[ThreadedNode] = None + + def set_root(self, root): + if root is None or isinstance(root, ThreadedNode): + super().set_root(root) + self.head = self.tail = self.root + self.dummy_node.set_left(self.root) + self.dummy_node.set_right(self.dummy_node) + return + raise TypeError("The 'root' parameter must be an " + "instance of ThreadedNode or its subclass.") + + def _construct(self, items: Optional[Sequence] = None) -> Self | None: + if not items or items[0] is None: + return None + for item in items: + self.insert(item) + return self + + @property # Level Order Traversal -> Tree to array + def array_repr(self) -> list[Any]: + array = [] + queue = Queue() + if self.root: + queue.enqueue(self.root) + while not queue.is_empty(): + node = queue.dequeue() + array.append(node.data) + if node.left_is_child: + queue.enqueue(node.left) + if node.right_is_child: + queue.enqueue(node.right) + + return array + + def convert_from(self, root: Any) -> None: + def insert_inorder(node: ThreadedNode | None) -> None: + if not node: + return + insert_inorder(node.left) + array.append(node) + insert_inorder(node.right) + + def clone_binary_tree(node) -> Optional[ThreadedNode]: + if not node: + return None + return ThreadedNode(node.data, + clone_binary_tree(node.left), + clone_binary_tree(node.right)) + + array: list[ThreadedNode] = [] + self.set_root(clone_binary_tree(root)) + + # Storing inorder traversal in queue + insert_inorder(self.root) + for n in range(len(array)): + if not array[n].left: + array[n].set_left( + self.dummy_node if not n else array[n - 1] + ) + if not array[n].right: + array[n].set_right( + self.dummy_node if n == len(array) - 1 else array[n + 1] + ) + + self.head, self.tail = array[0], array[-1] + self.dummy_node.set_left(self.root) + self.dummy_node.set_left_is_child(True) + + def insert(self, data: Any, root: Optional[ThreadedNode] = None) -> None: + root = root or self.root + if not root: + node = ThreadedNode(data, self.dummy_node, self.dummy_node) + node.set_left_is_child(False) + node.set_right_is_child(False) + self.set_root(node) + self.head = self.tail = self.root + self.dummy_node.set_left(self.root) + self.dummy_node.set_left_is_child(True) + return + + node = ThreadedNode(data) + left = right = False + + while root: + + if data is None: + break + if root.data > data: + if not root.left_is_child: + # will insert the child as left child + left = True + break + else: + root = root.left + elif root.data < data: + if not root.right_is_child: + # will insert the child as right child + right = True + break + else: + root = root.right + else: + warnings.warn( + f"Insertion unsuccessful. Item '{data}' already exists " + "in Tree", DuplicateNodeWarning) + return + + if left and root: + node.set_left(root.left) + root.set_left(node) + node.set_left_is_child(root.left_is_child) + root.set_left_is_child(True) + node.set_right(root) + + elif right and root: + node.set_right(root.right) + root.set_right(node) + node.set_right_is_child(root.right_is_child) + root.set_right_is_child(True) + node.set_left(root) + + if node.left is self.dummy_node: + self.head = node + elif node.right is self.dummy_node: + self.tail = node + + # DFS Traversal without using stack + def inorder(self) -> list[Any]: + ref = self.head + array = [] + while ref and ref is not self.dummy_node: + array.append(ref.data) + if not ref.right_is_child: + ref = ref.right + else: + node = ref.right + while node.left_is_child: + node = node.left + ref = node + return array + + +if __name__ == '__main__': + T = ThreadedBinaryTree([10, 30], ThreadedNode(20)) + print(T) + # print(T.root) + # print(T.root.left) + # print(T.root.right is T.dummy_node) + T.insert(110) + print(T) + print(ThreadedBinaryTree([110, 10, 20])) diff --git a/datastax/Trees/__init__.py b/datastax/Trees/__init__.py index 3b8ac1d..94001dc 100644 --- a/datastax/Trees/__init__.py +++ b/datastax/Trees/__init__.py @@ -1,29 +1,29 @@ -from .avl_tree import AVLTree, AVLNode -from .binary_search_tree import BinarySearchTree -from .binary_tree import BinaryTree, TreeNode -from .expression_tree import ExpressionTree -from .fibonacci_tree import FibonacciTree -from .heap_tree import HeapTree, HeapNode -from .huffman_tree import HuffmanTree, HuffmanNode, HuffmanTable -from .min_heap_tree import MinHeapTree -from .min_segment_tree import MinSegmentTree -from .red_black_tree import RedBlackTree, RedBlackNode -from .splay_tree import SplayTree, SplayNode -from .sum_segment_tree import SumSegmentTree, SegmentNode -from .threaded_binary_tree import ThreadedBinaryTree, ThreadedNode +from .BinaryTree import BinaryTree +from .BinarySearchTree import BinarySearchTree +from .AVLTree import AVLTree +from .RedBlackTree import RedBlackTree +from .SplayTree import SplayTree +from .HeapTree import HeapTree +from .MinHeapTree import MinHeapTree +from .HuffmanTree import HuffmanTree +from .FibonacciTree import FibonacciTree +from .ExpressionTree import ExpressionTree +from .SumSegmentTree import SumSegmentTree +from .MinSegmentTree import MinSegmentTree +from .ThreadedBinaryTree import ThreadedBinaryTree __all__ = [ - "BinaryTree", "TreeNode", + "BinaryTree", "BinarySearchTree", - "AVLTree", "AVLNode", - "HeapTree", "HeapNode", + "AVLTree", + "RedBlackTree", + "SplayTree", + "HeapTree", "MinHeapTree", + "HuffmanTree", + "FibonacciTree", "ExpressionTree", - "ThreadedBinaryTree", "ThreadedNode", - "SumSegmentTree", "SegmentNode", + "SumSegmentTree", "MinSegmentTree", - "HuffmanTree", "HuffmanNode", "HuffmanTable", - "RedBlackTree", "RedBlackNode", - "FibonacciTree", - "SplayTree", "SplayNode" + "ThreadedBinaryTree", ] diff --git a/datastax/Trees/threaded_binary_tree.py b/datastax/Trees/threaded_binary_tree.py deleted file mode 100644 index 68f7695..0000000 --- a/datastax/Trees/threaded_binary_tree.py +++ /dev/null @@ -1,121 +0,0 @@ -# Threaded Binary Tree Implementation -from __future__ import annotations - -import warnings -from typing import Optional, Any, Union - -from datastax.errors import DuplicateNodeWarning, ExplicitInsertionWarning -from datastax.Trees import TreeNode, AVLNode, HeapNode -from datastax.Trees.AbstractTrees import threaded_binary_tree -from datastax.Trees.AbstractTrees.threaded_binary_tree import ThreadedNode - -rootNode = Union[TreeNode, AVLNode, HeapNode] - - -class ThreadedBinaryTree(threaded_binary_tree.ThreadedBinaryTree): - def convert_to_tbt(self, root: rootNode) -> None: - def insert_inorder(node: Optional[ThreadedNode]) -> None: - if not node: - return - insert_inorder(node.left) - array.append(node) - insert_inorder(node.right) - - def clone_binary_tree(node: rootNode) -> Optional[ThreadedNode]: - if not node: - return None - return ThreadedNode(node.data, - clone_binary_tree(node.left), - clone_binary_tree(node.right)) - - array: list[ThreadedNode] = [] - self._root = clone_binary_tree(root) - - # Storing inorder traversal in queue - insert_inorder(self._root) - for n in range(len(array)): - if not array[n].left: - array[n].left = self.dummy_node if not n else array[n - 1] - if not array[n].right: - array[n].right = self.dummy_node if n == len(array) - 1 \ - else array[n + 1] - - self.head, self.tail = array[0], array[-1] - self.dummy_node.left = self.root - self.dummy_node.left_is_child = True - - def insert(self, data: Any, root: ThreadedNode = None) -> None: - if not isinstance(self.tree, ThreadedBinaryTree): - warnings.warn( - "Can't insert in Threaded Tree with explicit insertion logic" - "of Foreign Tree Logic", - ExplicitInsertionWarning - ) - return - root = root or self.root - node = ThreadedNode(data) - if not root: - self._root = node - node.left = node.right = self.dummy_node - self.head = self.tail = self.root - self.dummy_node.left = self.root - self.dummy_node.left_is_child = True - return - - left = right = False - while root: - if data is None: - break - if root.data > data: - if not root.left_is_child: - # will insert the child as left child - left = True - break - else: - root = root.left - elif root.data < data: - if not root.right_is_child: - # will insert the child as right child - right = True - break - else: - root = root.right - else: - warnings.warn( - f"Insertion unsuccessful. Item '{data}' already exists " - "in Tree", DuplicateNodeWarning) - return - - if left and root: - node.left = root.left - root.left = node - node.left_is_child = root.left_is_child - root.left_is_child = True - node.right = root - - elif right and root: - node.right = root.right - root.right = node - node.right_is_child = root.right_is_child - root.right_is_child = True - node.left = root - - if node.left is self.dummy_node: - self.head = node - elif node.right is self.dummy_node: - self.tail = node - - # DFS Traversal without using stack - def inorder(self) -> list[Any]: - ref: ThreadedNode = self.head - array: list[Any] = [] - while ref is not self.dummy_node: - array.append(ref.data) - if not ref.right_is_child: - ref = ref.right - else: - node = ref.right - while node.left_is_child: - node = node.left - ref = node - return array diff --git a/datastax/errors.py b/datastax/errors.py deleted file mode 100644 index e3ff5f9..0000000 --- a/datastax/errors.py +++ /dev/null @@ -1,97 +0,0 @@ -# from datastax import linkedlists as ll, Trees, arrays -from typing import Any - - -class Error(Exception): - def __init__(self, message: str): - self.message = message - - def __str__(self): - return f'{self.message}' - - -class OverFlowError(Error): - def __init__(self, data_type: Any): - data_structure = type(data_type).__name__ - operation = '' - if data_structure in ['Queue', 'PriorityQueue']: - operation = 'ENQUEUE' - elif data_structure == 'Stack': - operation = 'PUSH' - - message = f"{data_structure} is already full, " \ - f"can't perform {operation} operation any further" - super().__init__(message) - - -class UnderFlowError(Error): - def __init__(self, data_type: Any): - data_structure = type(data_type).__name__ - operation = '' - if data_structure in ['Queue', 'PriorityQueue']: - operation = 'DEQUEUE' - elif data_structure == 'Stack': - operation = 'POP' - - message = f"{data_structure} is already empty, " \ - f"can't perform {operation} operation any further" - super().__init__(message) - - -class PathNotGivenError(Error): - def __init__(self, data_type: Any): - data_structure = type(data_type).__name__ - message = f"{data_structure} already contains root node. Path " \ - f"required for inserting non root nodes in tree" - super().__init__(message) - - -class PathNotFoundError(Error): - def __init__(self, data_type: Any): - data_structure = type(data_type).__name__ - message = f"Path doesn't exist in {data_structure}" - super().__init__(message) - - -class InvalidExpressionError(Error): - def __init__(self, data_type: Any = 'ExpressionTree'): - data_structure = type(data_type).__name__ - message = f"Can't construct {data_structure}. " \ - "Check if any operands or operators are missing." - super().__init__(message) - - -class UnmatchedBracketPairError(Error): - def __init__(self, data_type: Any, expression: str): - data_structure = type(data_type).__name__ - - message = f"Can't construct {data_structure}. " \ - f"Bracket Pairs not matching in {expression}. " - # Bracket was not closed - if expression.count('(') > expression.count(')'): - message += "'(' was never closed." - # Bracket pairs don't match - else: - message += "')' has no pair '('." - - super().__init__(message) - - -class PathAlreadyOccupiedWarning(UserWarning): - pass - - -class NodeNotFoundWarning(UserWarning): - pass - - -class DuplicateNodeWarning(UserWarning): - pass - - -class DeletionFromEmptyTreeWarning(UserWarning): - pass - - -class ExplicitInsertionWarning(UserWarning): - pass