diff --git a/datastax/Nodes/AVLNode.py b/datastax/Nodes/AVLNode.py new file mode 100644 index 0000000..96b9516 --- /dev/null +++ b/datastax/Nodes/AVLNode.py @@ -0,0 +1,21 @@ +from typing import Any, Self, Optional +from datastax.Nodes.TreeNode import TreeNode + + +class AVLNode(TreeNode): + _height = 1 + + def __init__(self, data: Any, + left: Optional[Self] = None, + right: Optional[Self] = None): + super().__init__(data, left, right) + + @property + def height(self): + return self._height + + def set_height(self, height: int): + if isinstance(height, int): + self._height = height + return + raise TypeError("The 'height' parameter must be an integer") diff --git a/datastax/Nodes/AbstractNodes/DoublyNode.py b/datastax/Nodes/AbstractNodes/DoublyNode.py index d7ccce2..f2dad62 100644 --- a/datastax/Nodes/AbstractNodes/DoublyNode.py +++ b/datastax/Nodes/AbstractNodes/DoublyNode.py @@ -1,4 +1,4 @@ -from abc import ABC as AbstractClass +from abc import ABC as AbstractClass, abstractmethod from typing import Optional, Self from datastax.Nodes.AbstractNodes.Node import Node @@ -27,3 +27,7 @@ def __str__(self): ) dow = f" └────╨{'─' * width}╨────┘\n" return top + mid + dow + + @abstractmethod + def set_prev(self, prev: Self): + ... diff --git a/datastax/Nodes/AbstractNodes/HuffmanNode.py b/datastax/Nodes/AbstractNodes/HuffmanNode.py new file mode 100644 index 0000000..bbb2717 --- /dev/null +++ b/datastax/Nodes/AbstractNodes/HuffmanNode.py @@ -0,0 +1,80 @@ +from datastax.Nodes.AbstractNodes.TreeNode import TreeNode +from datastax.Utils import Commons +from abc import ABC as AbstractClass, abstractmethod + + +class HuffmanNode(TreeNode, AbstractClass): + _frequency: int + + @property + def frequency(self): + return self._frequency + + 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 Commons.repr(value), + values + ) + ) + max_width = max(len(Commons.repr(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"{Commons.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 += ( + Commons.node_builder(values[1], per_piece) + + Commons.node_builder(values[2], per_piece) + ) + elif values[1]: + string_builder += f"{' ' * (hpw + 1)}┌{'─' * hpw}┘\n" + string_builder += Commons.node_builder(values[1], per_piece) + else: + string_builder += f"{' ' * (per_piece - 1)} └{'─' * hpw}┐\n" + string_builder += ( + f"{' ' * (per_piece - 1)} " + f"{Commons.node_builder(values[2], per_piece)}" + ) + + return string_builder + + def preorder_print(self) -> None: + 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 Commons.repr(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]}" + + print(string_builder) + + @abstractmethod + def set_frequency(self, frequency: int): + ... diff --git a/datastax/Nodes/AbstractNodes/Node.py b/datastax/Nodes/AbstractNodes/Node.py index e264506..02c9af2 100644 --- a/datastax/Nodes/AbstractNodes/Node.py +++ b/datastax/Nodes/AbstractNodes/Node.py @@ -4,7 +4,7 @@ class Node(AbstractClass): - data: Optional[Any] + data: Any _next: Optional[Self] @property @@ -22,5 +22,5 @@ def __str__(self): return top + mid + dow @abstractmethod - def set_next(self, _next: Optional[Self]): + def set_next(self, _next: Self): ... diff --git a/datastax/Nodes/AbstractNodes/RedBlackNode.py b/datastax/Nodes/AbstractNodes/RedBlackNode.py new file mode 100644 index 0000000..fec662a --- /dev/null +++ b/datastax/Nodes/AbstractNodes/RedBlackNode.py @@ -0,0 +1,106 @@ +from typing import Self, Optional +from datastax.Nodes.AbstractNodes.TreeNode import TreeNode +from datastax.Utils import Commons, Colors +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 RedBlackNode(TreeNode, AbstractClass): + _parent: Optional[Self] = None + _color: int + + @property + def parent(self): + return self._parent + + @property + def color(self): + return self._color + + def __str__(self): + values = list( + map( + lambda node: "" if node is None else Commons.format( + node.color, Commons.repr(node.data) + ), [self, self.left, self.right] + ) + ) + max_width = max( + len(Commons.repr(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}" + f"{Commons.redblack_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 += Commons.redblack_node_builder( + values[1], per_piece + ) + Commons.redblack_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 = Commons.redblack_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"{Commons.redblack_node_builder(values[2], per_piece)}" + ) + string_builder += f" {reset}\n{extra_line}" + return string_builder + + def preorder_print(self) -> None: + values = list( + map( + lambda node: "" if node is None else Commons.format( + node.color, Commons.repr(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" + + print(string_builder) + + @abstractmethod + def set_parent(self, parent: Self): + ... + + @abstractmethod + def set_color(self, color: int): + ... diff --git a/datastax/Nodes/AbstractNodes/SegmentNode.py b/datastax/Nodes/AbstractNodes/SegmentNode.py new file mode 100644 index 0000000..b7e117a --- /dev/null +++ b/datastax/Nodes/AbstractNodes/SegmentNode.py @@ -0,0 +1,15 @@ +from datastax.Nodes.AbstractNodes.TreeNode import TreeNode +from abc import ABC as AbstractClass + + +class SegmentNode(TreeNode, AbstractClass): + left_index: int + right_index: int + + def __str__(self): + # to be overriden and implemented later + return super().__str__() + + def preorder_print(self) -> None: + # to be overriden and implemented later + super().preorder_print() diff --git a/datastax/Nodes/AbstractNodes/ThreadedNode.py b/datastax/Nodes/AbstractNodes/ThreadedNode.py new file mode 100644 index 0000000..06d3683 --- /dev/null +++ b/datastax/Nodes/AbstractNodes/ThreadedNode.py @@ -0,0 +1,106 @@ +from datastax.Utils import Commons +from datastax.Nodes.AbstractNodes.TreeNode import TreeNode +from abc import ABC as AbstractClass, abstractmethod + + +class ThreadedNode(TreeNode, AbstractClass): + _left_is_child: bool + _right_is_child: bool + + @property + def left_is_child(self): + return self._left_is_child + + @property + def right_is_child(self): + return self._right_is_child + + def __str__(self): + values = [ + self.data if self.data is not None else str(None), + self.left.data if self.left else None, + self.right.data if self.right else None + ] + values = list( + map(lambda value: Commons.repr(value), values) + ) + max_width = max(len(Commons.repr(data)) for data in values if data) + if max_width % 2: + max_width += 1 # To make max_width even + + padding = 6 + per_piece = 2 * (max_width + padding) + # 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 = f"{piece.center(wpn)}\n" + + root = values[0] + left = f"{values[1]}" + right = f"{values[2].center(wpn + 1)}\n" + + if self.left_is_child: + if self.right_is_child: + string_builder = ( + f"{root.center(wpn - 1)}".center(per_piece) + + f"\n{piece}{left}{right}" + ) + else: + string_builder = ( + f"{' ' * len(left)}\n" + f"{root.center(wpn)}".center(wpn - 1) + + f"│\n{piece}" + f"{left}" + ) + else: + string_builder = left + if self.right_is_child: + string_builder = ( + f"\n│{root.center(wpn)}".center(per_piece) + + f"\n{piece}{' ' * len(left)}" + f"\n{' ' * len(left)}{right}" + ) + else: + string_builder = ( + f"{right}" + f"│{root.center(wpn - 1)}│".center(per_piece) + + f"\n{piece}" + ) + + return string_builder + + def preorder_print(self) -> None: + values = [ + self.data if self.data is not None else str(None), + self.left.data if self.left else None, + self.right.data if self.right else None + ] + values = list( + map( + lambda value: str(value) if value is None else Commons.repr( + 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]}" + + print(string_builder) + + @abstractmethod + def set_left_is_child(self, is_child: bool): + ... + + @abstractmethod + def set_right_is_child(self, is_child: bool): + ... diff --git a/datastax/Nodes/AbstractNodes/TreeNode.py b/datastax/Nodes/AbstractNodes/TreeNode.py new file mode 100644 index 0000000..53a4279 --- /dev/null +++ b/datastax/Nodes/AbstractNodes/TreeNode.py @@ -0,0 +1,88 @@ +from typing import Self, Any, Optional +from abc import ABC as AbstractClass, abstractmethod +from datastax.Utils import Commons + + +class TreeNode(AbstractClass): + _left: Optional[Self] + data: Any + _right: Optional[Self] + + @property + def left(self): + return self._left + + @property + def right(self): + return self._right + + def __str__(self): + values = [ + self.data if self.data is not None else str(None), + self.left.data if self.left else None, + self.right.data if self.right else None + ] + values = list( + map(lambda value: Commons.repr(value), values) + ) + max_width = max(len(Commons.repr(data)) for data in values if data) + if max_width % 2: + max_width += 1 # To make max_width even + + "Building string from calculated values" + padding = 4 + per_piece = 2 * (max_width + padding) + string_builder = f"{Commons.node_builder(values[0], per_piece)}\n" + per_piece //= 2 + hpw = int(per_piece // 2 - 1) + if self.left: + if self.right: + string_builder += ( + f"{' ' * (hpw + 1)}" + f"┌{'─' * hpw}┴{'─' * hpw}┐\n" + ) + string_builder += Commons.node_builder( + values[1], per_piece + ) + Commons.node_builder(values[2], per_piece) + else: + string_builder += f"{' ' * (hpw + 1)}┌{'─' * hpw}┘\n" + string_builder += Commons.node_builder(values[1], per_piece) + elif self.right: + string_builder += f"{' ' * (per_piece - 1)} └{'─' * hpw}┐\n" + string_builder += ( + f"{' ' * (per_piece - 1)} " + f"{Commons.node_builder(values[2], per_piece)}" + ) + + return string_builder + + def preorder_print(self) -> None: + values = [ + self.data if self.data is not None else str(None), + self.left.data if self.left else None, + self.right.data if self.right else None + ] + values = list( + map( + lambda value: None if value is None else Commons.repr(value), + values + ) + ) + + string_builder = f'{values[0]}\n' + if self.left or self.right: + if self.left and self.right: + string_builder += f"├─▶ {values[1]}\n" + string_builder += f"└─▶ {values[2]}" + else: + string_builder += f"└─▶ {values[1] or values[2]}" + + print(string_builder) + + @abstractmethod + def set_left(self, left: Self): + ... + + @abstractmethod + def set_right(self, right: Self): + ... diff --git a/datastax/Nodes/AbstractNodes/__init__.py b/datastax/Nodes/AbstractNodes/__init__.py index dbccec0..b9acf54 100644 --- a/datastax/Nodes/AbstractNodes/__init__.py +++ b/datastax/Nodes/AbstractNodes/__init__.py @@ -1,7 +1,17 @@ from .Node import Node from .DoublyNode import DoublyNode +from .TreeNode import TreeNode +from .RedBlackNode import RedBlackNode +from .HuffmanNode import HuffmanNode +from .SegmentNode import SegmentNode +from .ThreadedNode import ThreadedNode __all__ = [ - Node, - DoublyNode + 'Node', + 'DoublyNode', + 'TreeNode', + 'RedBlackNode', + 'HuffmanNode', + 'SegmentNode', + 'ThreadedNode' ] diff --git a/datastax/Nodes/DoublyNode.py b/datastax/Nodes/DoublyNode.py index c028246..18d14c0 100644 --- a/datastax/Nodes/DoublyNode.py +++ b/datastax/Nodes/DoublyNode.py @@ -1,10 +1,9 @@ from typing import Any, Self, Optional - from datastax.Nodes.Node import Node from datastax.Nodes.AbstractNodes import DoublyNode as AbstractNode -class DoublyNode(AbstractNode, Node): +class DoublyNode(Node, AbstractNode): def __init__(self, data: Any, _next: Optional[Self] = None, prev: Optional[Self] = None): @@ -12,13 +11,14 @@ def __init__(self, data: Any, self.set_next(_next) self.set_prev(prev) - def set_next(self, _next: Optional[Self] = None): - if _next is not None and not isinstance(_next, DoublyNode): - raise TypeError("The 'next' parameter must be an " - "instance of DoublyNode or its subclass.") - super().set_next(_next) + def set_next(self, _next: Optional[Self]): + if _next is None or isinstance(_next, DoublyNode): + super().set_next(_next) + return + raise TypeError("The 'next' parameter must be an " + "instance of DoublyNode or its subclass.") - def set_prev(self, prev: Optional[Self] = None): + def set_prev(self, prev: Optional[Self]): if prev is None or isinstance(prev, DoublyNode): self._prev = prev return diff --git a/datastax/Nodes/HeapNode.py b/datastax/Nodes/HeapNode.py new file mode 100644 index 0000000..32d899b --- /dev/null +++ b/datastax/Nodes/HeapNode.py @@ -0,0 +1,34 @@ +from typing import Any, Self, Optional +from datastax.Nodes.TreeNode import TreeNode + + +class HeapNode(TreeNode): + _parent: Optional[Self] = None + _prev_leaf: Optional[Self] = None + + def __init__(self, data: Any, + left: Optional[Self] = None, + right: Optional[Self] = None): + super().__init__(data, left, right) + + @property + def parent(self): + return self._parent + + @property + def prev_leaf(self): + return self._prev_leaf + + def set_parent(self, parent: Self | None): + if parent is None or isinstance(parent, HeapNode): + self._parent = parent + return + raise TypeError("The 'parent' parameter must be an " + "instance of HeapNode or its subclass.") + + def set_prev_leaf(self, prev_leaf: Self | None): + if prev_leaf is None or isinstance(prev_leaf, HeapNode): + self._prev_leaf = prev_leaf + return + raise TypeError("The 'prev_leaf' parameter must be an " + "instance of HeapNode or its subclass.") diff --git a/datastax/Nodes/HuffmanNode.py b/datastax/Nodes/HuffmanNode.py new file mode 100644 index 0000000..e1a572c --- /dev/null +++ b/datastax/Nodes/HuffmanNode.py @@ -0,0 +1,19 @@ +from typing import Self, Any, Optional +from datastax.Nodes.TreeNode import TreeNode +from datastax.Nodes.AbstractNodes import HuffmanNode as AbstractNode + + +class HuffmanNode(TreeNode, AbstractNode): + def __init__(self, data: Any, + left: Optional[Self] = None, + right: Optional[Self] = None, + frequency: Optional[int] = 1): + super().__init__(data, left, right) + self.set_frequency(1 if frequency is None else frequency) + + def set_frequency(self, frequency: int): + if isinstance(frequency, int): + self._frequency = frequency + return + raise TypeError("The 'frequency' parameter must be an " + "instance of HuffmanNode or its subclass.") diff --git a/datastax/Nodes/Node.py b/datastax/Nodes/Node.py index 0f89e5a..ae7f953 100644 --- a/datastax/Nodes/Node.py +++ b/datastax/Nodes/Node.py @@ -1,5 +1,4 @@ from typing import Any, Self, Optional - from datastax.Nodes.AbstractNodes import Node as AbstractNode @@ -9,7 +8,7 @@ def __init__(self, data: Any, self.data = data self.set_next(_next) - def set_next(self, _next: Optional[Self] = None): + def set_next(self, _next: Optional[Self]): if _next is None or isinstance(_next, Node): self._next = _next return diff --git a/datastax/Nodes/RedBlackNode.py b/datastax/Nodes/RedBlackNode.py new file mode 100644 index 0000000..064218e --- /dev/null +++ b/datastax/Nodes/RedBlackNode.py @@ -0,0 +1,26 @@ +from typing import Self, Optional, Any +from datastax.Nodes.TreeNode import TreeNode +from datastax.Nodes.AbstractNodes import RedBlackNode as AbstractNode +from datastax.Utils import ColorCodes + + +class RedBlackNode(TreeNode, AbstractNode): + def __init__(self, data: Any, + left: Optional[Self] = None, + right: Optional[Self] = None, + color: int = ColorCodes.RED): + super().__init__(data, left, right) + self.set_color(color) + + def set_parent(self, parent: Self | None): + if parent is None or isinstance(parent, RedBlackNode): + self._parent = parent + return + raise TypeError("The 'parent' parameter must be an " + "instance of RedBlackNode or its subclass.") + + def set_color(self, color: int): + if color in (0, 1): + self._color = color + return + raise TypeError("The 'color' parameter must be 0 or 1") diff --git a/datastax/Nodes/SegmentNode.py b/datastax/Nodes/SegmentNode.py new file mode 100644 index 0000000..39cde89 --- /dev/null +++ b/datastax/Nodes/SegmentNode.py @@ -0,0 +1,13 @@ +from typing import Any, Self, Optional +from datastax.Nodes.TreeNode import TreeNode +from datastax.Nodes.AbstractNodes import SegmentNode as AbstractNode + + +class SegmentNode(TreeNode, AbstractNode): + left_index = 0 + right_index = 0 + + def __init__(self, data: Any, + left: Optional[Self] = None, + right: Optional[Self] = None): + super().__init__(data, left, right) diff --git a/datastax/Nodes/SplayNode.py b/datastax/Nodes/SplayNode.py new file mode 100644 index 0000000..2812a75 --- /dev/null +++ b/datastax/Nodes/SplayNode.py @@ -0,0 +1,22 @@ +from typing import Any, Self, Optional +from datastax.Nodes.TreeNode import TreeNode + + +class SplayNode(TreeNode): + _parent: Optional[Self] = None + + def __init__(self, data: Any, + left: Optional[Self] = None, + right: Optional[Self] = None): + super().__init__(data, left, right) + + @property + def parent(self): + return self._parent + + def set_parent(self, parent: Self | None): + if parent is None or isinstance(parent, SplayNode): + self._parent = parent + return + raise TypeError("The 'parent' parameter must be an " + "instance of SplayNode or its subclass.") diff --git a/datastax/Nodes/ThreadedNode.py b/datastax/Nodes/ThreadedNode.py new file mode 100644 index 0000000..872587b --- /dev/null +++ b/datastax/Nodes/ThreadedNode.py @@ -0,0 +1,24 @@ +from typing import Self, Optional, Any +from datastax.Nodes.TreeNode import TreeNode +from datastax.Nodes.AbstractNodes import ThreadedNode as AbstractNode + + +class ThreadedNode(TreeNode, AbstractNode): + def __init__(self, data: Any, + left: Optional[Self] = None, + right: Optional[Self] = None): + super().__init__(data, left, right) + self.set_left_is_child(bool(self.left)) + self.set_right_is_child(bool(self.right)) + + def set_left_is_child(self, is_child: bool): + if isinstance(is_child, bool): + self._left_is_child = is_child + return + raise TypeError("The 'is_child' parameter must be bool") + + def set_right_is_child(self, is_child: bool): + if isinstance(is_child, bool): + self._right_is_child = is_child + return + raise TypeError("The 'is_child' parameter must be bool") diff --git a/datastax/Nodes/TreeNode.py b/datastax/Nodes/TreeNode.py new file mode 100644 index 0000000..0e6f96b --- /dev/null +++ b/datastax/Nodes/TreeNode.py @@ -0,0 +1,25 @@ +from typing import Any, Optional, Self +from datastax.Nodes.AbstractNodes import TreeNode as AbstractNode + + +class TreeNode(AbstractNode): + def __init__(self, data: Any, + left: Optional[Self] = None, + right: Optional[Self] = None): + self.data = data + self.set_left(left) + self.set_right(right) + + def set_left(self, left: Self | None): + if left is None or isinstance(left, TreeNode): + self._left = left + return + raise TypeError("The 'left' parameter must be an " + "instance of TreeNode or its subclass.") + + def set_right(self, right: Self | None): + if right is None or isinstance(right, TreeNode): + self._right = right + return + raise TypeError("The 'right' parameter must be an " + "instance of TreeNode or its subclass.") diff --git a/datastax/Nodes/__init__.py b/datastax/Nodes/__init__.py index dbccec0..c9b1219 100644 --- a/datastax/Nodes/__init__.py +++ b/datastax/Nodes/__init__.py @@ -1,7 +1,23 @@ from .Node import Node from .DoublyNode import DoublyNode +from .TreeNode import TreeNode +from .AVLNode import AVLNode +from .RedBlackNode import RedBlackNode +from .SplayNode import SplayNode +from .HeapNode import HeapNode +from .HuffmanNode import HuffmanNode +from .SegmentNode import SegmentNode +from .ThreadedNode import ThreadedNode __all__ = [ - Node, - DoublyNode + 'Node', + 'DoublyNode', + 'TreeNode', + 'AVLNode', + 'RedBlackNode', + 'SplayNode', + 'HeapNode', + 'HuffmanNode', + 'SegmentNode', + 'ThreadedNode' ]