data_structures

 1from .node import NodeCore, NodePriorityQueue, TreeNode
 2from .linked_list import LinkedList
 3from .doubly_linked_list import Node, DoublyLinkedList
 4from .queues import QueueArray, LinkedQueue, CircularQueue, PriorityQueue
 5from .stack import StackArray, LinkedStack
 6from .avl_tree import AVLTreeNode, AVLTree
 7from .red_black_tree import Color, RBTreeNode, RedBlackTree
 8from .binary_search_tree import (
 9    traversal_keywords,
10    copy_keywords,
11    default_keyword,
12    BinarySearchTree,
13)
14
15__all__ = [
16    "NodeCore",
17    "NodePriorityQueue",
18    "TreeNode",
19    "LinkedList",
20    "Node",
21    "DoublyLinkedList",
22    "QueueArray",
23    "LinkedQueue",
24    "CircularQueue",
25    "PriorityQueue",
26    "StackArray",
27    "LinkedStack",
28    "traversal_keywords",
29    "copy_keywords",
30    "default_keyword",
31    "BinarySearchTree",
32    "AVLTreeNode",
33    "AVLTree",
34    "Color",
35    "RBTreeNode",
36    "RedBlackTree",
37]
class NodeCore:
 5class NodeCore:
 6    """This is the fundamental class that's used & inherited for linear data structures.
 7
 8    Attributes:
 9        value (any): Value (data) stored in node.
10        next_node (Node | None): A pointer to the next node if exists.
11    """
12
13    def __init__(self, value, next_node=None):
14        """Initialize a NodeCore object.
15
16        Args:
17            value (Any, optional): First value to add to the list. Defaults to None.
18            next_node (NodeCore, optional): Node class used to create list nodes. Defaults to Node.
19        """
20        self._value = value
21        self._next_node = next_node
22
23    @property
24    def value(self):
25        """Any: The stored value in the `Node` object."""
26        return self._value
27
28    @property
29    def next_node(self):
30        """Return the next node. If there are no next node, returns `None`."""
31        return self._next_node
32
33    @next_node.setter
34    def next_node(self, node):
35        """Set the next node.
36
37        Args:
38            node (Node | None): new `Node` object or None if it's the tail node.
39        """
40        self._next_node = node

This is the fundamental class that's used & inherited for linear data structures.

Attributes:
  • value (any): Value (data) stored in node.
  • next_node (Node | None): A pointer to the next node if exists.
NodeCore(value, next_node=None)
13    def __init__(self, value, next_node=None):
14        """Initialize a NodeCore object.
15
16        Args:
17            value (Any, optional): First value to add to the list. Defaults to None.
18            next_node (NodeCore, optional): Node class used to create list nodes. Defaults to Node.
19        """
20        self._value = value
21        self._next_node = next_node

Initialize a NodeCore object.

Arguments:
  • value (Any, optional): First value to add to the list. Defaults to None.
  • next_node (NodeCore, optional): Node class used to create list nodes. Defaults to Node.
value
23    @property
24    def value(self):
25        """Any: The stored value in the `Node` object."""
26        return self._value

Any: The stored value in the Node object.

next_node
28    @property
29    def next_node(self):
30        """Return the next node. If there are no next node, returns `None`."""
31        return self._next_node

Return the next node. If there are no next node, returns None.

class NodePriorityQueue(data_structures.NodeCore):
43class NodePriorityQueue(NodeCore):
44    """This is the base node used to construct a priority queue. Inherits the NodeCore.
45
46    Attributes:
47        value (any): Value (data) stored in node.
48        priority (int): Priority value for each node.
49        next_node (Node | None): Next node for the current node.
50    """
51
52    def __init__(self, value, priority=None, next_node=None):
53        """Initialize a NodePriorityQueue object. This is used for creating priority queues. Inherits NodeCore.
54
55        Args:
56            value (Any, optional): First value to add to the list. Defaults to None.
57            next_node (type, optional): Node class used to create list nodes. Defaults to Node.
58            priority (int): The priority value for the node. This will set the position of the node in the list.
59        """
60        super().__init__(value, next_node)
61        self.priority = priority

This is the base node used to construct a priority queue. Inherits the NodeCore.

Attributes:
  • value (any): Value (data) stored in node.
  • priority (int): Priority value for each node.
  • next_node (Node | None): Next node for the current node.
NodePriorityQueue(value, priority=None, next_node=None)
52    def __init__(self, value, priority=None, next_node=None):
53        """Initialize a NodePriorityQueue object. This is used for creating priority queues. Inherits NodeCore.
54
55        Args:
56            value (Any, optional): First value to add to the list. Defaults to None.
57            next_node (type, optional): Node class used to create list nodes. Defaults to Node.
58            priority (int): The priority value for the node. This will set the position of the node in the list.
59        """
60        super().__init__(value, next_node)
61        self.priority = priority

Initialize a NodePriorityQueue object. This is used for creating priority queues. Inherits NodeCore.

Arguments:
  • value (Any, optional): First value to add to the list. Defaults to None.
  • next_node (type, optional): Node class used to create list nodes. Defaults to Node.
  • priority (int): The priority value for the node. This will set the position of the node in the list.
priority
class TreeNode:
 64class TreeNode:
 65    """This is the base node used when implementing binary search trees.
 66
 67    Args:
 68        value (Any): The stored value in the node.
 69        left_child (Node | None): Left child of the current node.
 70        right_child (Node | None): Right child of the current node.
 71        parent (Node | None): Parent node of the current node.
 72    """
 73
 74    def __init__(self, value=None, left_child=None, right_child=None, parent=None):
 75        """Initialize a TreeNode object. Used in data_structures.BinarySearchTree.
 76
 77        Args:
 78            value (Any): The stored value in the node.
 79            left_child (Node | None): Left child of the current node.
 80            right_child (Node | None): Right child of the current node.
 81            parent (Node | None): Parent node of the current node.
 82        """
 83        self._value = value
 84        self._left_child = left_child
 85        self._right_child = right_child
 86        self._parent = parent
 87
 88    @property
 89    def value(self):
 90        """Any: The stored value in the `TreeNode` object."""
 91        return self._value
 92
 93    @value.setter
 94    def value(self, value):
 95        """Set a new value for the node.
 96
 97        Args:
 98            value (Any): The new value of the node.
 99        """
100        self._value = value
101
102    @property
103    def left_child(self):
104        """None | TreeNode: Returns left child the TreeNode object if exists."""
105        return self._left_child
106
107    @left_child.setter
108    def left_child(self, left_child):
109        """Set the left_child of the TreeNode.
110
111        Args:
112            left_child (TreeNode | None): Left child of the node object.
113        """
114        self._left_child = left_child
115
116    @property
117    def right_child(self):
118        """TreeNode: Returns the right child TreeNode object."""
119        return self._right_child
120
121    @right_child.setter
122    def right_child(self, right_child):
123        """Set the right_child of the TreeNode.
124
125        Args:
126            right_child (TreeNode | None): Right child of the node object.
127        """
128        self._right_child = right_child
129
130    @property
131    def left_value(self):
132        """Any: Value of the left_child node."""
133        return self._left_child.value if self._left_child else None
134
135    @property
136    def right_value(self):
137        """Any: Value of the right_child node."""
138        return self._right_child.value if self._right_child else None
139
140    @property
141    def parent(self):
142        """None | TreeNode: Returns the parent node (TreeNode) object if exists."""
143        return self._parent
144
145    @parent.setter
146    def parent(self, parent):
147        """Set the parent node.
148
149        Args:
150            parent (TreeNode | None): Parent of the node object.
151        """
152        self._parent = parent

This is the base node used when implementing binary search trees.

Arguments:
  • value (Any): The stored value in the node.
  • left_child (Node | None): Left child of the current node.
  • right_child (Node | None): Right child of the current node.
  • parent (Node | None): Parent node of the current node.
TreeNode(value=None, left_child=None, right_child=None, parent=None)
74    def __init__(self, value=None, left_child=None, right_child=None, parent=None):
75        """Initialize a TreeNode object. Used in data_structures.BinarySearchTree.
76
77        Args:
78            value (Any): The stored value in the node.
79            left_child (Node | None): Left child of the current node.
80            right_child (Node | None): Right child of the current node.
81            parent (Node | None): Parent node of the current node.
82        """
83        self._value = value
84        self._left_child = left_child
85        self._right_child = right_child
86        self._parent = parent

Initialize a TreeNode object. Used in data_structures.BinarySearchTree.

Arguments:
  • value (Any): The stored value in the node.
  • left_child (Node | None): Left child of the current node.
  • right_child (Node | None): Right child of the current node.
  • parent (Node | None): Parent node of the current node.
value
88    @property
89    def value(self):
90        """Any: The stored value in the `TreeNode` object."""
91        return self._value

Any: The stored value in the TreeNode object.

left_child
102    @property
103    def left_child(self):
104        """None | TreeNode: Returns left child the TreeNode object if exists."""
105        return self._left_child

None | TreeNode: Returns left child the TreeNode object if exists.

right_child
116    @property
117    def right_child(self):
118        """TreeNode: Returns the right child TreeNode object."""
119        return self._right_child

TreeNode: Returns the right child TreeNode object.

left_value
130    @property
131    def left_value(self):
132        """Any: Value of the left_child node."""
133        return self._left_child.value if self._left_child else None

Any: Value of the left_child node.

right_value
135    @property
136    def right_value(self):
137        """Any: Value of the right_child node."""
138        return self._right_child.value if self._right_child else None

Any: Value of the right_child node.

parent
140    @property
141    def parent(self):
142        """None | TreeNode: Returns the parent node (TreeNode) object if exists."""
143        return self._parent

None | TreeNode: Returns the parent node (TreeNode) object if exists.

class LinkedList:
  7class LinkedList:
  8    """This is the singly linked list implementation.
  9
 10    Attributes:
 11        _Node (Any): This is the node object used to create a list. Custom node objects can be used. Defaults to NodeCore.
 12        head_node (None or Node): The first node in the list.
 13        tail_node (None or Node): The last node in the list.
 14        size (int): The number of elements in the list.
 15        value (Any): Value of the node.
 16    """
 17
 18    def __init__(self, value=None, node=Node):
 19        """Initialize the `LinkedList`.
 20
 21        Args:
 22            head_node (None or Node): The first node in the list.
 23            tail_node (None or Node): The last node in the list.
 24            size (int): The number of elements in the list.
 25            value (Any): Value of the node.
 26            node (any | Node): Node container to construct a doubly linked list. Defaults to Node.
 27        """
 28        self._Node = node
 29        if value is not None:
 30            self.head_node = self._Node(value)
 31            self.tail_node = self.head_node
 32            self._size = 1
 33        else:
 34            self.head_node = None
 35            self.tail_node = None
 36            self._size = 0
 37
 38    @property
 39    def size(self):
 40        """int: How many elements in the linked list."""
 41        return self._size
 42
 43    def __str__(self):
 44        """str: A string representation of nodes in the list."""
 45        return "[ " + " ".join(str(node) for node in self) + " ]"
 46
 47    def __len__(self):
 48        """int: How many elements in the linked list."""
 49        return self._size
 50
 51    def __getitem__(self, index):
 52        """Make LinkedList class indexable.
 53
 54        Args:
 55            index (int): Index of `LinkedList` object to return the value at that position.
 56
 57        Raises:
 58            IndexError: If the `index` is out of range.
 59        """
 60        if index < 0:
 61            index += self._size
 62        if not 0 <= index < self._size:
 63            raise IndexError(f"Index {index} is out of bounds")
 64        else:
 65            return self.find_by_index(index)
 66
 67    def __iter__(self):
 68        """Dunder method to make `LinkedList` iterable."""
 69        current_node = self.head_node
 70        while current_node:
 71            yield current_node.value
 72            current_node = current_node.next_node
 73
 74    def insert_head(self, value):
 75        """Inserts a value at head. Wraps the value inside the Node object.
 76
 77        Args:
 78            value (any): Value to be inserted.
 79        """
 80        new_node = self._Node(value)
 81        new_node.next_node = self.head_node
 82        self._size += 1
 83        self.head_node = new_node
 84        if self._size == 1:
 85            self.tail_node = new_node
 86
 87    def insert_tail(self, value):
 88        """Inserts a value at tail. Wraps the value inside the Node object.
 89
 90        Args:
 91            value (any): Value to be inserted.
 92        """
 93        new_node = self._Node(value)
 94        if self._size == 0:
 95            self.head_node = new_node
 96            self.tail_node = self.head_node
 97        else:
 98            self.tail_node.next_node = new_node
 99            self.tail_node = new_node
100        self._size += 1
101
102    def insert_index(self, index, value):
103        """Insert the value inside the given index. Shifts the elements coming after that index.
104
105        Args:
106            index (int): Index position to insert the node.
107            value (any): Value wrapped inside a Node to be inserted.
108
109        Raises:
110            IndexError: If the `index` is out of range.
111        """
112        if index < 0 or index > self._size - 1:
113            raise IndexError(f"Index {index} out of bounds for insertion")
114        else:
115            if index == 0:
116                self.insert_head(value)
117                return
118            current_node = self.head_node
119            counter = 0
120            while counter < index - 1:
121                current_node = current_node.next_node
122                counter += 1
123            new_node = self._Node(value)
124            next_node = current_node.next_node
125            current_node.next_node = new_node
126            self._size += 1
127            new_node.next_node = next_node
128
129    def delete_head(self):
130        """Delete the first element in the list.
131
132        Raises:
133            Exception: If the list is empty.
134        """
135        if self.head_node is None:
136            raise Exception("Cannot delete from an empty list")
137        second_node = self.head_node.next_node
138        self._size -= 1
139        self.head_node = second_node
140
141    def delete_tail(self):
142        """Delete the last element in the list.
143
144        Raises:
145            Exception: If the `head_node` is None.
146        """
147        if self.head_node is None:
148            raise Exception("Cannot delete from an empty list")
149        elif self._size == 1:
150            self.head_node = None
151            self.tail_node = None
152        else:
153            current_node = self.head_node
154            while current_node.next_node.next_node is not None:
155                current_node = current_node.next_node
156            self.tail_node = current_node
157            current_node.next_node = None
158        self._size -= 1
159
160    def delete_index(self, index):
161        """Delete the element at the given index.
162
163        Args:
164            index (int): The index where the node will be deleted.
165
166        Raises:
167            IndexError: If the `index` is out of range.
168        """
169        if index < 0 or index > self._size - 1:
170            raise IndexError(
171                f"Index {index} is out of bounds for deletion - List size: {self._size}"
172            )
173        current_node = self.head_node
174        counter = 0
175        if self._size == 1 or index == 0:
176            self.delete_head()
177        else:
178            while (
179                counter < index - 1
180            ):  # get to the previous node of the node we want to delete
181                current_node = current_node.next_node
182                counter += 1
183            next_node = current_node.next_node.next_node
184            current_node.next_node = next_node
185            self._size -= 1
186
187    def delete_value(self, value):
188        """Delete a node with the given value.
189
190        Args:
191            value (any): Value that a node contains.
192
193        Raises:
194            Exception: If the list is empty.
195            ValueError: If the `value` is not found in the list.
196        """
197        current_node = self.head_node
198        previous_node = None
199
200        while current_node is not None:
201            if current_node.value == value:
202                if previous_node is None:
203                    self.delete_head()
204                    return
205                else:
206                    previous_node.next_node = current_node.next_node
207                    self._size -= 1
208                    return
209            previous_node = current_node
210            current_node = current_node.next_node
211
212        raise ValueError(f"{value} is not in the list")
213
214    def find_by_index(self, index):
215        """Find and return the value of a node of a given index.
216
217        Args:
218            index (int): The index of the node whose value is wanted.
219
220        Returns:
221            any: value of the node at `index`
222
223        Raises:
224            IndexError: If the `index` is out of range.
225        """
226        if index < 0:
227            index += self._size
228        if index > self._size - 1:
229            raise IndexError(f"Index {index} is out of bounds for check")
230        current_node = self.head_node
231        counter = 0
232        while current_node is not None:
233            if counter == index:
234                return current_node.value
235            else:
236                current_node = current_node.next_node
237                counter += 1
238
239    def traverse(self):
240        """Traverse and print the each value in the list."""
241        current_node = self.head_node
242        while current_node is not None:
243            print(current_node.value)
244            current_node = current_node.next_node

This is the singly linked list implementation.

Attributes:
  • _Node (Any): This is the node object used to create a list. Custom node objects can be used. Defaults to NodeCore.
  • head_node (None or Node): The first node in the list.
  • tail_node (None or Node): The last node in the list.
  • size (int): The number of elements in the list.
  • value (Any): Value of the node.
LinkedList(value=None, node=<class 'NodeCore'>)
18    def __init__(self, value=None, node=Node):
19        """Initialize the `LinkedList`.
20
21        Args:
22            head_node (None or Node): The first node in the list.
23            tail_node (None or Node): The last node in the list.
24            size (int): The number of elements in the list.
25            value (Any): Value of the node.
26            node (any | Node): Node container to construct a doubly linked list. Defaults to Node.
27        """
28        self._Node = node
29        if value is not None:
30            self.head_node = self._Node(value)
31            self.tail_node = self.head_node
32            self._size = 1
33        else:
34            self.head_node = None
35            self.tail_node = None
36            self._size = 0

Initialize the LinkedList.

Arguments:
  • head_node (None or Node): The first node in the list.
  • tail_node (None or Node): The last node in the list.
  • size (int): The number of elements in the list.
  • value (Any): Value of the node.
  • node (any | Node): Node container to construct a doubly linked list. Defaults to Node.
size
38    @property
39    def size(self):
40        """int: How many elements in the linked list."""
41        return self._size

int: How many elements in the linked list.

def insert_head(self, value):
74    def insert_head(self, value):
75        """Inserts a value at head. Wraps the value inside the Node object.
76
77        Args:
78            value (any): Value to be inserted.
79        """
80        new_node = self._Node(value)
81        new_node.next_node = self.head_node
82        self._size += 1
83        self.head_node = new_node
84        if self._size == 1:
85            self.tail_node = new_node

Inserts a value at head. Wraps the value inside the Node object.

Arguments:
  • value (any): Value to be inserted.
def insert_tail(self, value):
 87    def insert_tail(self, value):
 88        """Inserts a value at tail. Wraps the value inside the Node object.
 89
 90        Args:
 91            value (any): Value to be inserted.
 92        """
 93        new_node = self._Node(value)
 94        if self._size == 0:
 95            self.head_node = new_node
 96            self.tail_node = self.head_node
 97        else:
 98            self.tail_node.next_node = new_node
 99            self.tail_node = new_node
100        self._size += 1

Inserts a value at tail. Wraps the value inside the Node object.

Arguments:
  • value (any): Value to be inserted.
def insert_index(self, index, value):
102    def insert_index(self, index, value):
103        """Insert the value inside the given index. Shifts the elements coming after that index.
104
105        Args:
106            index (int): Index position to insert the node.
107            value (any): Value wrapped inside a Node to be inserted.
108
109        Raises:
110            IndexError: If the `index` is out of range.
111        """
112        if index < 0 or index > self._size - 1:
113            raise IndexError(f"Index {index} out of bounds for insertion")
114        else:
115            if index == 0:
116                self.insert_head(value)
117                return
118            current_node = self.head_node
119            counter = 0
120            while counter < index - 1:
121                current_node = current_node.next_node
122                counter += 1
123            new_node = self._Node(value)
124            next_node = current_node.next_node
125            current_node.next_node = new_node
126            self._size += 1
127            new_node.next_node = next_node

Insert the value inside the given index. Shifts the elements coming after that index.

Arguments:
  • index (int): Index position to insert the node.
  • value (any): Value wrapped inside a Node to be inserted.
Raises:
  • IndexError: If the index is out of range.
def delete_head(self):
129    def delete_head(self):
130        """Delete the first element in the list.
131
132        Raises:
133            Exception: If the list is empty.
134        """
135        if self.head_node is None:
136            raise Exception("Cannot delete from an empty list")
137        second_node = self.head_node.next_node
138        self._size -= 1
139        self.head_node = second_node

Delete the first element in the list.

Raises:
  • Exception: If the list is empty.
def delete_tail(self):
141    def delete_tail(self):
142        """Delete the last element in the list.
143
144        Raises:
145            Exception: If the `head_node` is None.
146        """
147        if self.head_node is None:
148            raise Exception("Cannot delete from an empty list")
149        elif self._size == 1:
150            self.head_node = None
151            self.tail_node = None
152        else:
153            current_node = self.head_node
154            while current_node.next_node.next_node is not None:
155                current_node = current_node.next_node
156            self.tail_node = current_node
157            current_node.next_node = None
158        self._size -= 1

Delete the last element in the list.

Raises:
  • Exception: If the head_node is None.
def delete_index(self, index):
160    def delete_index(self, index):
161        """Delete the element at the given index.
162
163        Args:
164            index (int): The index where the node will be deleted.
165
166        Raises:
167            IndexError: If the `index` is out of range.
168        """
169        if index < 0 or index > self._size - 1:
170            raise IndexError(
171                f"Index {index} is out of bounds for deletion - List size: {self._size}"
172            )
173        current_node = self.head_node
174        counter = 0
175        if self._size == 1 or index == 0:
176            self.delete_head()
177        else:
178            while (
179                counter < index - 1
180            ):  # get to the previous node of the node we want to delete
181                current_node = current_node.next_node
182                counter += 1
183            next_node = current_node.next_node.next_node
184            current_node.next_node = next_node
185            self._size -= 1

Delete the element at the given index.

Arguments:
  • index (int): The index where the node will be deleted.
Raises:
  • IndexError: If the index is out of range.
def delete_value(self, value):
187    def delete_value(self, value):
188        """Delete a node with the given value.
189
190        Args:
191            value (any): Value that a node contains.
192
193        Raises:
194            Exception: If the list is empty.
195            ValueError: If the `value` is not found in the list.
196        """
197        current_node = self.head_node
198        previous_node = None
199
200        while current_node is not None:
201            if current_node.value == value:
202                if previous_node is None:
203                    self.delete_head()
204                    return
205                else:
206                    previous_node.next_node = current_node.next_node
207                    self._size -= 1
208                    return
209            previous_node = current_node
210            current_node = current_node.next_node
211
212        raise ValueError(f"{value} is not in the list")

Delete a node with the given value.

Arguments:
  • value (any): Value that a node contains.
Raises:
  • Exception: If the list is empty.
  • ValueError: If the value is not found in the list.
def find_by_index(self, index):
214    def find_by_index(self, index):
215        """Find and return the value of a node of a given index.
216
217        Args:
218            index (int): The index of the node whose value is wanted.
219
220        Returns:
221            any: value of the node at `index`
222
223        Raises:
224            IndexError: If the `index` is out of range.
225        """
226        if index < 0:
227            index += self._size
228        if index > self._size - 1:
229            raise IndexError(f"Index {index} is out of bounds for check")
230        current_node = self.head_node
231        counter = 0
232        while current_node is not None:
233            if counter == index:
234                return current_node.value
235            else:
236                current_node = current_node.next_node
237                counter += 1

Find and return the value of a node of a given index.

Arguments:
  • index (int): The index of the node whose value is wanted.
Returns:

any: value of the node at index

Raises:
  • IndexError: If the index is out of range.
def traverse(self):
239    def traverse(self):
240        """Traverse and print the each value in the list."""
241        current_node = self.head_node
242        while current_node is not None:
243            print(current_node.value)
244            current_node = current_node.next_node

Traverse and print the each value in the list.

class Node(data_structures.NodeCore):
 7class Node(NodeCore):
 8    """Base node for DoublyLinkedList. Inherits all attributes from NodeCore.
 9
10    Attributes:
11        prev_node (None | Node): Pointer to the previous node.
12    """
13
14    def __init__(self, value, next_node=None, prev_node=None):
15        """Initialize a Node.
16
17        Args:
18            value
19            value (any): Value (data) stored in node.
20            next_node (Node | None): A pointer to the next node if exists.
21            prev_node (Node | None): A pointer to the previous node if exists.
22        """
23        super().__init__(value, next_node)
24        self._prev_node = prev_node
25
26    @property
27    def prev_node(self):
28        """Node | None: Return the previous node."""
29        return self._prev_node
30
31    @prev_node.setter
32    def prev_node(self, value):
33        """Set the previous node's value.
34
35        Args:
36            value (any): Value of the previous `Node` object.
37        """
38        self._prev_node = value

Base node for DoublyLinkedList. Inherits all attributes from NodeCore.

Attributes:
  • prev_node (None | Node): Pointer to the previous node.
Node(value, next_node=None, prev_node=None)
14    def __init__(self, value, next_node=None, prev_node=None):
15        """Initialize a Node.
16
17        Args:
18            value
19            value (any): Value (data) stored in node.
20            next_node (Node | None): A pointer to the next node if exists.
21            prev_node (Node | None): A pointer to the previous node if exists.
22        """
23        super().__init__(value, next_node)
24        self._prev_node = prev_node

Initialize a Node.

Arguments:
  • value
  • value (any): Value (data) stored in node.
  • next_node (Node | None): A pointer to the next node if exists.
  • prev_node (Node | None): A pointer to the previous node if exists.
prev_node
26    @property
27    def prev_node(self):
28        """Node | None: Return the previous node."""
29        return self._prev_node

Node | None: Return the previous node.

class DoublyLinkedList:
 41class DoublyLinkedList:
 42    """This is the doubly linked list implementation.
 43
 44    Attributes:
 45        _Node (Any): This is the node object used to create a list. Custom node objects can be used. Defaults to NodeCore.
 46        head_node (None or Node): The first node in the list.
 47        tail_node (None or Node): The last node in the list.
 48        size (int): The number of elements in the list.
 49        value (Any): Value of the node.
 50    """
 51
 52    def __init__(self, value=None, node=Node):
 53        """Initialize the `DoublyLinkedList`.
 54
 55        Args:
 56            head_node (None or Node): The first node in the list.
 57            tail_node (None or Node): The last node in the list.
 58            size (int): The number of elements in the list.
 59            value (Any): Value of the node.
 60            node (any | Node): Node container to construct a doubly linked list. Defaults to Node.
 61        """
 62        self._Node = node
 63        if value is not None:
 64            self.head_node = self._Node(value)
 65            self.tail_node = self.head_node
 66            self._size = 1
 67        else:
 68            self.head_node = None
 69            self.tail_node = None
 70            self._size = 0
 71
 72    def __str__(self):
 73        """Return the string representation of the `LinkedList` object."""
 74        return "[ " + " ".join(str(node) for node in self) + " ]"
 75
 76    def __len__(self):
 77        """Return the size of the linked list."""
 78        return self._size
 79
 80    def __getitem__(self, index):
 81        """Make LinkedList class indexable.
 82
 83        Args:
 84            index (int): Index of `LinkedList` object to return the value at that position.
 85
 86        Raises:
 87            IndexError: If the `index` is out of range.
 88        """
 89        if index < 0:
 90            index += self._size
 91        if not 0 <= index < self._size:
 92            raise IndexError(f"Index {index} is out of bounds")
 93        else:
 94            return self.find_by_index(index)
 95
 96    def __iter__(self):
 97        """Dunder method to make `LinkedList` iterable."""
 98        current_node = self.head_node
 99        while current_node:
100            yield current_node.value
101            current_node = current_node.next_node
102
103    def insert_head(self, value):
104        """Inserts a value at head. Wraps the value inside the Node object.
105
106        Args:
107            value (any): Value to be inserted.
108        """
109        if self._size == 0:
110            self.head_node = self._Node(value)
111            self.tail_node = self.head_node
112        else:
113            new_node = self._Node(value)
114            new_node.next_node = self.head_node
115            self.head_node.prev_node = new_node
116            self.head_node = new_node
117        self._size += 1
118
119    def insert_tail(self, value):
120        """Inserts a value at tail. Wraps the value inside the Node object.
121
122        Args:
123            value (any): Value to be inserted.
124        """
125        new_node = self._Node(value)
126        if self._size == 0:
127            self.head_node = new_node
128            self.tail_node = self.head_node
129        else:
130            self.tail_node.next_node = new_node
131            final_node = self.tail_node.next_node
132            final_node.prev_node = self.tail_node
133            self.tail_node = final_node
134        self._size += 1
135
136    def delete_head(self):
137        """Delete the first element in the list.
138
139        Raises:
140            Exception: If the list is empty.
141        """
142        if self._size == 0:
143            raise Exception("Cannot delete from an empty list")
144        elif self._size == 1:
145            self.head_node = None
146            self.tail_node = None
147        else:
148            self.head_node = self.head_node.next_node
149            self.head_node.prev_node = None
150        self._size -= 1
151
152    def delete_tail(self):
153        """Delete the last element in the list.
154
155        Raises:
156            Exception: If the size is 0.
157        """
158        if self._size == 0:
159            raise Exception("Cannot delete from an empty list")
160        elif self._size == 1:
161            self.head_node = None
162            self.tail_node = None
163        else:
164            self.tail_node = self.tail_node.prev_node
165            self.tail_node.next_node = None
166        self._size -= 1
167
168    def delete_index(self, index):
169        """Delete the element at the given index.
170
171        Args:
172            index (int): The index where the node will be deleted.
173
174        Raises:
175            IndexError: If the `index` is out of range.
176        """
177        if index < 0 or index > self._size - 1:
178            raise IndexError(f"Index {index} is out of bounds")
179        current_node = self.head_node
180        counter = 0
181        if self._size == 1 or index == 0:
182            self.delete_head()
183        else:
184            while counter < index - 1:
185                current_node = current_node.next_node
186                counter += 1
187            next_node = current_node.next_node.next_node
188            current_node.next_node = next_node
189            if next_node is None:
190                self.tail_node = current_node
191            else:
192                next_node.prev_node = current_node
193            self._size -= 1
194            return
195
196    def delete_value(self, value):
197        """Delete a node with the given value.
198
199        Args:
200            value (any): Value that a node contains.
201
202        Raises:
203            Exception: If the list is empty.
204            ValueError: If the `value` is not found in the list.
205        """
206        if self._size == 0:
207            raise Exception(f"Cannot delete from an empty list")
208        else:
209            current_node = self.head_node
210            prev_node = None
211            while current_node is not None:
212                if current_node.value == value:
213                    if prev_node is None:  # if it's the first node
214                        self.delete_head()
215                    else:
216                        next_node = current_node.next_node
217                        prev_node.next_node = next_node
218                        if next_node is None:
219                            self.tail_node = prev_node
220                        else:
221                            next_node.prev_node = prev_node
222                        self._size -= 1
223                    return
224                prev_node = current_node
225                current_node = current_node.next_node
226        raise ValueError(f"{value} cannot be found in the list")
227
228    def find_by_index(self, index):
229        """Find and return the value of a node of a given index.
230
231        Args:
232            index (int): The index of the node whose value is wanted.
233
234        Returns:
235            any: value of the node at `index`
236
237        Raises:
238            IndexError: If the `index` is out of range.
239        """
240        if index < 0 or index > self._size - 1:
241            raise IndexError(f"Index {index} is out of bounds")
242        counter = 0
243        current_node = self.head_node
244        while current_node is not None:
245            if counter == index:
246                return current_node.value
247            current_node = current_node.next_node
248            counter += 1
249
250    def find_by_value(self, value):
251        """Find the first occurence of a node by value.
252
253        Args:
254            value (any): Value of the node.
255
256        Raises:
257            Exception: If the list is empty.
258            ValueError: If the `value` is not found in the list.
259        """
260        if self._size == 0:
261            raise Exception("Cannot find a value in an empty list.")
262        else:
263            current_node = self.head_node
264            idx = 0
265            while current_node is not None:
266                if current_node.value == value:
267                    print(f"{value} is found at index {idx}")
268                    return
269                else:
270                    current_node = current_node.next_node
271                    idx += 1
272        raise ValueError(f"{value} cannot be found in the list")
273
274    def reverse_find_by_value(self, value):
275        """Find the last occurence of the value. Starts traversing from the tail node.
276
277        Args:
278            value (any): Value of the node.
279
280        Raises:
281            Exception: If the `value` isn't found in the list.
282        """
283        if self._size == 0:
284            raise Exception("Cannot find a value in an empty list.")
285        current_node = self.tail_node
286        reverse_idx = 0
287        while current_node is not None:
288            if current_node.value == value:
289                print(f"{value} is found at index {self._size - reverse_idx - 1}")
290                return
291            current_node = current_node.prev_node
292            reverse_idx += 1
293        raise Exception("f{value} cannot be found in the list")
294
295    def traverse(self):
296        """Traverse the list and print each value."""
297        current_node = self.head_node
298        while current_node is not None:
299            print(current_node.value)
300            current_node = current_node.next_node

This is the doubly linked list implementation.

Attributes:
  • _Node (Any): This is the node object used to create a list. Custom node objects can be used. Defaults to NodeCore.
  • head_node (None or Node): The first node in the list.
  • tail_node (None or Node): The last node in the list.
  • size (int): The number of elements in the list.
  • value (Any): Value of the node.
DoublyLinkedList(value=None, node=<class 'Node'>)
52    def __init__(self, value=None, node=Node):
53        """Initialize the `DoublyLinkedList`.
54
55        Args:
56            head_node (None or Node): The first node in the list.
57            tail_node (None or Node): The last node in the list.
58            size (int): The number of elements in the list.
59            value (Any): Value of the node.
60            node (any | Node): Node container to construct a doubly linked list. Defaults to Node.
61        """
62        self._Node = node
63        if value is not None:
64            self.head_node = self._Node(value)
65            self.tail_node = self.head_node
66            self._size = 1
67        else:
68            self.head_node = None
69            self.tail_node = None
70            self._size = 0

Initialize the DoublyLinkedList.

Arguments:
  • head_node (None or Node): The first node in the list.
  • tail_node (None or Node): The last node in the list.
  • size (int): The number of elements in the list.
  • value (Any): Value of the node.
  • node (any | Node): Node container to construct a doubly linked list. Defaults to Node.
def insert_head(self, value):
103    def insert_head(self, value):
104        """Inserts a value at head. Wraps the value inside the Node object.
105
106        Args:
107            value (any): Value to be inserted.
108        """
109        if self._size == 0:
110            self.head_node = self._Node(value)
111            self.tail_node = self.head_node
112        else:
113            new_node = self._Node(value)
114            new_node.next_node = self.head_node
115            self.head_node.prev_node = new_node
116            self.head_node = new_node
117        self._size += 1

Inserts a value at head. Wraps the value inside the Node object.

Arguments:
  • value (any): Value to be inserted.
def insert_tail(self, value):
119    def insert_tail(self, value):
120        """Inserts a value at tail. Wraps the value inside the Node object.
121
122        Args:
123            value (any): Value to be inserted.
124        """
125        new_node = self._Node(value)
126        if self._size == 0:
127            self.head_node = new_node
128            self.tail_node = self.head_node
129        else:
130            self.tail_node.next_node = new_node
131            final_node = self.tail_node.next_node
132            final_node.prev_node = self.tail_node
133            self.tail_node = final_node
134        self._size += 1

Inserts a value at tail. Wraps the value inside the Node object.

Arguments:
  • value (any): Value to be inserted.
def delete_head(self):
136    def delete_head(self):
137        """Delete the first element in the list.
138
139        Raises:
140            Exception: If the list is empty.
141        """
142        if self._size == 0:
143            raise Exception("Cannot delete from an empty list")
144        elif self._size == 1:
145            self.head_node = None
146            self.tail_node = None
147        else:
148            self.head_node = self.head_node.next_node
149            self.head_node.prev_node = None
150        self._size -= 1

Delete the first element in the list.

Raises:
  • Exception: If the list is empty.
def delete_tail(self):
152    def delete_tail(self):
153        """Delete the last element in the list.
154
155        Raises:
156            Exception: If the size is 0.
157        """
158        if self._size == 0:
159            raise Exception("Cannot delete from an empty list")
160        elif self._size == 1:
161            self.head_node = None
162            self.tail_node = None
163        else:
164            self.tail_node = self.tail_node.prev_node
165            self.tail_node.next_node = None
166        self._size -= 1

Delete the last element in the list.

Raises:
  • Exception: If the size is 0.
def delete_index(self, index):
168    def delete_index(self, index):
169        """Delete the element at the given index.
170
171        Args:
172            index (int): The index where the node will be deleted.
173
174        Raises:
175            IndexError: If the `index` is out of range.
176        """
177        if index < 0 or index > self._size - 1:
178            raise IndexError(f"Index {index} is out of bounds")
179        current_node = self.head_node
180        counter = 0
181        if self._size == 1 or index == 0:
182            self.delete_head()
183        else:
184            while counter < index - 1:
185                current_node = current_node.next_node
186                counter += 1
187            next_node = current_node.next_node.next_node
188            current_node.next_node = next_node
189            if next_node is None:
190                self.tail_node = current_node
191            else:
192                next_node.prev_node = current_node
193            self._size -= 1
194            return

Delete the element at the given index.

Arguments:
  • index (int): The index where the node will be deleted.
Raises:
  • IndexError: If the index is out of range.
def delete_value(self, value):
196    def delete_value(self, value):
197        """Delete a node with the given value.
198
199        Args:
200            value (any): Value that a node contains.
201
202        Raises:
203            Exception: If the list is empty.
204            ValueError: If the `value` is not found in the list.
205        """
206        if self._size == 0:
207            raise Exception(f"Cannot delete from an empty list")
208        else:
209            current_node = self.head_node
210            prev_node = None
211            while current_node is not None:
212                if current_node.value == value:
213                    if prev_node is None:  # if it's the first node
214                        self.delete_head()
215                    else:
216                        next_node = current_node.next_node
217                        prev_node.next_node = next_node
218                        if next_node is None:
219                            self.tail_node = prev_node
220                        else:
221                            next_node.prev_node = prev_node
222                        self._size -= 1
223                    return
224                prev_node = current_node
225                current_node = current_node.next_node
226        raise ValueError(f"{value} cannot be found in the list")

Delete a node with the given value.

Arguments:
  • value (any): Value that a node contains.
Raises:
  • Exception: If the list is empty.
  • ValueError: If the value is not found in the list.
def find_by_index(self, index):
228    def find_by_index(self, index):
229        """Find and return the value of a node of a given index.
230
231        Args:
232            index (int): The index of the node whose value is wanted.
233
234        Returns:
235            any: value of the node at `index`
236
237        Raises:
238            IndexError: If the `index` is out of range.
239        """
240        if index < 0 or index > self._size - 1:
241            raise IndexError(f"Index {index} is out of bounds")
242        counter = 0
243        current_node = self.head_node
244        while current_node is not None:
245            if counter == index:
246                return current_node.value
247            current_node = current_node.next_node
248            counter += 1

Find and return the value of a node of a given index.

Arguments:
  • index (int): The index of the node whose value is wanted.
Returns:

any: value of the node at index

Raises:
  • IndexError: If the index is out of range.
def find_by_value(self, value):
250    def find_by_value(self, value):
251        """Find the first occurence of a node by value.
252
253        Args:
254            value (any): Value of the node.
255
256        Raises:
257            Exception: If the list is empty.
258            ValueError: If the `value` is not found in the list.
259        """
260        if self._size == 0:
261            raise Exception("Cannot find a value in an empty list.")
262        else:
263            current_node = self.head_node
264            idx = 0
265            while current_node is not None:
266                if current_node.value == value:
267                    print(f"{value} is found at index {idx}")
268                    return
269                else:
270                    current_node = current_node.next_node
271                    idx += 1
272        raise ValueError(f"{value} cannot be found in the list")

Find the first occurence of a node by value.

Arguments:
  • value (any): Value of the node.
Raises:
  • Exception: If the list is empty.
  • ValueError: If the value is not found in the list.
def reverse_find_by_value(self, value):
274    def reverse_find_by_value(self, value):
275        """Find the last occurence of the value. Starts traversing from the tail node.
276
277        Args:
278            value (any): Value of the node.
279
280        Raises:
281            Exception: If the `value` isn't found in the list.
282        """
283        if self._size == 0:
284            raise Exception("Cannot find a value in an empty list.")
285        current_node = self.tail_node
286        reverse_idx = 0
287        while current_node is not None:
288            if current_node.value == value:
289                print(f"{value} is found at index {self._size - reverse_idx - 1}")
290                return
291            current_node = current_node.prev_node
292            reverse_idx += 1
293        raise Exception("f{value} cannot be found in the list")

Find the last occurence of the value. Starts traversing from the tail node.

Arguments:
  • value (any): Value of the node.
Raises:
  • Exception: If the value isn't found in the list.
def traverse(self):
295    def traverse(self):
296        """Traverse the list and print each value."""
297        current_node = self.head_node
298        while current_node is not None:
299            print(current_node.value)
300            current_node = current_node.next_node

Traverse the list and print each value.

class QueueArray:
10class QueueArray:
11    """Queue data structure using basic arrays.
12
13    Attributes:
14        array (List): Empty array. This will contain the data.
15    """
16
17    def __init__(self, *args):
18        """Initialize a QueueArray.
19
20        The constructor will accept a variable number of elements and enqueue each
21        element into the array.
22
23        Args:
24            *args: Variable length argument list. Those are basically elements to be stored.
25        """
26        self.array = []
27        for arg in args:
28            self.enqueue(arg)
29
30    @property
31    def size(self):
32        """int: Size of the array."""
33        return len(self.array)
34
35    @property
36    def front(self):
37        """any: The value at front if the queue is not empty. Otherwise None."""
38        return self.array[0] if not self.is_empty() else None
39
40    @property
41    def rear(self):
42        """any: The value at the rear if the queue is not empty. Otherwise None."""
43        return self.array[-1] if not self.is_empty() else None
44
45    def __str__(self):
46        """str: String representation of the QueueArray."""
47        return "[ " + " ".join(str(element) for element in self.array) + " ]"
48
49    def __len__(self):
50        """int: Size of the QueueArray."""
51        return self.size
52
53    def __getitem__(self, index):
54        """Dunder method to make QueueArray indexable.
55
56        Args:
57            index (int): Index of `LinkedList` object to return the value at that position.
58
59        Raises:
60            IndexError: If the `index` is out of range.
61        """
62        if index < 0:
63            index += self.size
64        if not 0 <= index < self.size:
65            raise IndexError(f"Index {index} is out of bounds")
66        else:
67            return self.array[index]
68
69    def enqueue(self, value):
70        """Enqueue the element inside the array.
71
72        Args:
73            value (any): Value to be enqueued.
74        """
75        self.array.append(value)
76
77    def dequeue(self):  # O(n)
78        """Dequeue the element from the array.
79
80        Since this operation is using `.pop(0)`, it has O(n) time complexity because
81        all the values in the array is shifted.
82
83        Args:
84            value (any): Value to be dequeued.
85        """
86        return self.array.pop(0)
87
88    def is_empty(self):
89        """bool: Return True if the size is 0, else False."""
90        return self.size == 0
91
92    def clear(self):
93        """Clears the whole array of the QueueArray."""
94        self.array.clear()
95
96    def copy(self):
97        """Return a deep copy of the QueueArray object with the same values in the same order."""
98        return QueueArray(*self.array)

Queue data structure using basic arrays.

Attributes:
  • array (List): Empty array. This will contain the data.
QueueArray(*args)
17    def __init__(self, *args):
18        """Initialize a QueueArray.
19
20        The constructor will accept a variable number of elements and enqueue each
21        element into the array.
22
23        Args:
24            *args: Variable length argument list. Those are basically elements to be stored.
25        """
26        self.array = []
27        for arg in args:
28            self.enqueue(arg)

Initialize a QueueArray.

The constructor will accept a variable number of elements and enqueue each element into the array.

Arguments:
  • *args: Variable length argument list. Those are basically elements to be stored.
array
size
30    @property
31    def size(self):
32        """int: Size of the array."""
33        return len(self.array)

int: Size of the array.

front
35    @property
36    def front(self):
37        """any: The value at front if the queue is not empty. Otherwise None."""
38        return self.array[0] if not self.is_empty() else None

any: The value at front if the queue is not empty. Otherwise None.

rear
40    @property
41    def rear(self):
42        """any: The value at the rear if the queue is not empty. Otherwise None."""
43        return self.array[-1] if not self.is_empty() else None

any: The value at the rear if the queue is not empty. Otherwise None.

def enqueue(self, value):
69    def enqueue(self, value):
70        """Enqueue the element inside the array.
71
72        Args:
73            value (any): Value to be enqueued.
74        """
75        self.array.append(value)

Enqueue the element inside the array.

Arguments:
  • value (any): Value to be enqueued.
def dequeue(self):
77    def dequeue(self):  # O(n)
78        """Dequeue the element from the array.
79
80        Since this operation is using `.pop(0)`, it has O(n) time complexity because
81        all the values in the array is shifted.
82
83        Args:
84            value (any): Value to be dequeued.
85        """
86        return self.array.pop(0)

Dequeue the element from the array.

Since this operation is using .pop(0), it has O(n) time complexity because all the values in the array is shifted.

Arguments:
  • value (any): Value to be dequeued.
def is_empty(self):
88    def is_empty(self):
89        """bool: Return True if the size is 0, else False."""
90        return self.size == 0

bool: Return True if the size is 0, else False.

def clear(self):
92    def clear(self):
93        """Clears the whole array of the QueueArray."""
94        self.array.clear()

Clears the whole array of the QueueArray.

def copy(self):
96    def copy(self):
97        """Return a deep copy of the QueueArray object with the same values in the same order."""
98        return QueueArray(*self.array)

Return a deep copy of the QueueArray object with the same values in the same order.

class LinkedQueue(data_structures.LinkedList):
101class LinkedQueue(LinkedList):
102    """A queue that's using LinkedList object."""
103
104    def __init__(self, *values):
105        """Initialize a queue based on linked list data structure.
106
107        Args:
108            *values (List): Variable length argument list. Those are the elements to be stored in the linked queue.
109        """
110        super().__init__()
111        for value in values:
112            self.insert_tail(value)
113
114    @property
115    def front(self):
116        """any: The value at front if the linked queue is not empty. Otherwise None."""
117        return self.head_node.value if not self.is_empty() else None
118
119    @property
120    def rear(self):
121        """any: The value at rear if the linked queue is not empty. Otherwise None."""
122        return self.tail_node.value if not self.is_empty() else None
123
124    def enqueue(self, value):  # O(1)
125        """Enqueue an element inside the linked queue.
126
127        This method uses the `insert_tail(value)` method from the LinkedList.
128
129        Args:
130            value (any): Value to insert.
131        """
132        self.insert_tail(value)
133
134    def dequeue(self):  # O(1)
135        """Dequeue an element from the linked queue.
136
137        This method uses the `insert_tail(value)` method from the LinkedList.
138
139        Raises:
140            Exception: If the list is empty.
141        """
142        return self.delete_head()
143
144    def is_empty(self):
145        """bool: Return True if the size is 0, else False."""
146        return self.size == 0
147
148    def clear(self):
149        """Clears the whole array of the LinkedQueue.
150
151        Raises:
152            Exception: If the list is empty.
153        """
154        while not self.is_empty():
155            self.delete_head()
156
157    def copy(self):
158        """Returns a deep copy of the LinkedQueue object with the same values."""
159        return LinkedQueue(*self)

A queue that's using LinkedList object.

LinkedQueue(*values)
104    def __init__(self, *values):
105        """Initialize a queue based on linked list data structure.
106
107        Args:
108            *values (List): Variable length argument list. Those are the elements to be stored in the linked queue.
109        """
110        super().__init__()
111        for value in values:
112            self.insert_tail(value)

Initialize a queue based on linked list data structure.

Arguments:
  • *values (List): Variable length argument list. Those are the elements to be stored in the linked queue.
front
114    @property
115    def front(self):
116        """any: The value at front if the linked queue is not empty. Otherwise None."""
117        return self.head_node.value if not self.is_empty() else None

any: The value at front if the linked queue is not empty. Otherwise None.

rear
119    @property
120    def rear(self):
121        """any: The value at rear if the linked queue is not empty. Otherwise None."""
122        return self.tail_node.value if not self.is_empty() else None

any: The value at rear if the linked queue is not empty. Otherwise None.

def enqueue(self, value):
124    def enqueue(self, value):  # O(1)
125        """Enqueue an element inside the linked queue.
126
127        This method uses the `insert_tail(value)` method from the LinkedList.
128
129        Args:
130            value (any): Value to insert.
131        """
132        self.insert_tail(value)

Enqueue an element inside the linked queue.

This method uses the insert_tail(value) method from the LinkedList.

Arguments:
  • value (any): Value to insert.
def dequeue(self):
134    def dequeue(self):  # O(1)
135        """Dequeue an element from the linked queue.
136
137        This method uses the `insert_tail(value)` method from the LinkedList.
138
139        Raises:
140            Exception: If the list is empty.
141        """
142        return self.delete_head()

Dequeue an element from the linked queue.

This method uses the insert_tail(value) method from the LinkedList.

Raises:
  • Exception: If the list is empty.
def is_empty(self):
144    def is_empty(self):
145        """bool: Return True if the size is 0, else False."""
146        return self.size == 0

bool: Return True if the size is 0, else False.

def clear(self):
148    def clear(self):
149        """Clears the whole array of the LinkedQueue.
150
151        Raises:
152            Exception: If the list is empty.
153        """
154        while not self.is_empty():
155            self.delete_head()

Clears the whole array of the LinkedQueue.

Raises:
  • Exception: If the list is empty.
def copy(self):
157    def copy(self):
158        """Returns a deep copy of the LinkedQueue object with the same values."""
159        return LinkedQueue(*self)

Returns a deep copy of the LinkedQueue object with the same values.

class CircularQueue:
162class CircularQueue:
163    """Circular queue implementation to handle empty space problem at the front.
164
165    Attributes:
166        capacity (int): Capacity of the circular queue. Default size is 50.
167        array (List[any]): The list to be behaved as a circular queue.
168        front_idx (int): Index of the front value of the array. -1 if the array is empty.
169        rear_idx (int): Index of the rear value of the array. -1 if the array is empty.
170        size (int): Size of the circular array.
171    """
172
173    def __init__(self, *values, capacity=50):
174        """Initialize a CircularQueue object.
175
176        Args:
177            capacity (int): Capacity of the circular queue. Default size is 50.
178            array (List[any]): The list to be behaved as a circular queue.
179            front_idx (int): Index of the front value of the array. -1 if the array is empty.
180            rear_idx (int): Index of the rear value of the array. -1 if the array is empty.
181            size (int): Size of the circular array.
182            *values (List): Variable length argument list. Those are the elements to be stored in the linked queue.
183        """
184        self.capacity = capacity
185        self.array = [None] * self.capacity
186        self.front_idx = -1
187        self.rear_idx = -1
188        self._size = 0
189        for value in values:
190            self.enqueue(value)
191
192    @property
193    def size(self):
194        """int: Size of the array."""
195        return self._size
196
197    @property
198    def front(self):
199        """any: The value at front if the circular queue is not empty. Otherwise None."""
200        return self.array[self.front_idx] if not self.is_empty() else None
201
202    @property
203    def rear(self):
204        """any: The value at rear if the circular queue is not empty. Otherwise None."""
205        return self.array[self.rear_idx] if not self.is_empty() else None
206
207    def __str__(self):
208        """str: String representation of the CircularQueue object."""
209        string = "[ "
210        idx = self.front_idx
211        for _ in range(self.size):
212            string += str(self.array[idx]) + " "
213            idx = (idx + 1) % self.capacity
214        return string + "]"
215
216    def __repr__(self):
217        """str: Representation of CircularQueue object for debugging."""
218        return (
219            f"CircularQueue(array={self.array}, "
220            f"capacity={self.capacity}, "
221            f"front_idx={self.front_idx}, "
222            f"rear_idx={self.rear_idx}, "
223            f"size={self.size})"
224        )
225
226    def __len__(self):
227        """int: Size of the array."""
228        return self._size
229
230    def __getitem__(self, index):
231        """Make CircularQueue class indexable.
232
233        Args:
234            index (int): Index of `CircularQueue` object to return the value at that position.
235
236        Raises:
237            IndexError: If the `index` is out of range.
238        """
239        if index < 0:
240            index += self.size
241        if not 0 <= index < self.size:
242            raise IndexError(f"Index {index} is out of bounds")
243        else:
244            physical_index = (self.front_idx + index) % self.capacity
245            return self.array[physical_index]
246
247    def __iter__(self):
248        """Dunder method to make `CircularQueue` iterable."""
249        for i in range(self.size):
250            physical_index = (self.front_idx + i) % self.capacity
251            yield self.array[physical_index]
252
253    def enqueue(self, value):
254        """Enqueue data for a circular queue.
255
256        This is a manual implementation instead of using %. Each time a new insertion is
257        made, the front_idx and rear_idx changes.
258
259        Args:
260            value (any): To enqueue inside the CircularQueue.
261
262        Raises:
263            Exception: If the CircularQueue is full.
264        """
265        if self.is_full():
266            raise Exception("The circular queue is full")
267        elif self.is_empty():
268            self.front_idx = self.rear_idx = 0
269        elif self.is_rear_idx_reached() and self.front_idx != 0:
270            self.rear_idx = 0
271        else:
272            self.rear_idx = self.rear_idx + 1
273        self.array[self.rear_idx] = value
274        self._size += 1
275
276    def dequeue(self):
277        """Dequeue an element from the CircularQueue.
278
279        Using the modular arithmetics, front and rear indices are updated.
280
281        Raises:
282            Exception: If the circular queue is empty.
283        """
284        if self.is_empty():
285            raise Exception("The circular queue is empty")
286        value = self.array[self.front_idx]
287        self.array[self.front_idx] = None
288        self.front_idx = (self.front_idx + 1) % self.capacity
289        self._size -= 1
290        if self._size == 0:
291            self.front_idx = -1
292            self.rear_idx = -1
293        return value
294
295    def is_empty(self):
296        """bool: Return True if the size is 0, else False."""
297        return self.size == 0
298
299    def is_full(self):
300        """bool: Return True if the queue is full capacity, else False."""
301        return (self.rear_idx + 1) % self.capacity == self.front_idx
302
303    def is_rear_idx_reached(self):
304        """bool: Return True if the `rear_idx` reached at the end, else False.
305
306        This is used to check if the rear_idx should be jumped to the front of the circular queue.
307        """
308        return self.rear_idx == self.capacity - 1
309
310    def clear(self):
311        """Clears the whole CircularQueue object.
312
313        Creates the default array representation. Reset the front and rear indices, set the size to 0.
314        """
315        self.array = [None] * self.capacity
316        self.front_idx = -1
317        self.rear_idx = -1
318        self._size = 0
319
320    def copy(self):
321        """Return a deep copy of the CircularQueue object."""
322        return CircularQueue(*self, capacity=self.capacity)

Circular queue implementation to handle empty space problem at the front.

Attributes:
  • capacity (int): Capacity of the circular queue. Default size is 50.
  • array (List[any]): The list to be behaved as a circular queue.
  • front_idx (int): Index of the front value of the array. -1 if the array is empty.
  • rear_idx (int): Index of the rear value of the array. -1 if the array is empty.
  • size (int): Size of the circular array.
CircularQueue(*values, capacity=50)
173    def __init__(self, *values, capacity=50):
174        """Initialize a CircularQueue object.
175
176        Args:
177            capacity (int): Capacity of the circular queue. Default size is 50.
178            array (List[any]): The list to be behaved as a circular queue.
179            front_idx (int): Index of the front value of the array. -1 if the array is empty.
180            rear_idx (int): Index of the rear value of the array. -1 if the array is empty.
181            size (int): Size of the circular array.
182            *values (List): Variable length argument list. Those are the elements to be stored in the linked queue.
183        """
184        self.capacity = capacity
185        self.array = [None] * self.capacity
186        self.front_idx = -1
187        self.rear_idx = -1
188        self._size = 0
189        for value in values:
190            self.enqueue(value)

Initialize a CircularQueue object.

Arguments:
  • capacity (int): Capacity of the circular queue. Default size is 50.
  • array (List[any]): The list to be behaved as a circular queue.
  • front_idx (int): Index of the front value of the array. -1 if the array is empty.
  • rear_idx (int): Index of the rear value of the array. -1 if the array is empty.
  • size (int): Size of the circular array.
  • *values (List): Variable length argument list. Those are the elements to be stored in the linked queue.
capacity
array
front_idx
rear_idx
size
192    @property
193    def size(self):
194        """int: Size of the array."""
195        return self._size

int: Size of the array.

front
197    @property
198    def front(self):
199        """any: The value at front if the circular queue is not empty. Otherwise None."""
200        return self.array[self.front_idx] if not self.is_empty() else None

any: The value at front if the circular queue is not empty. Otherwise None.

rear
202    @property
203    def rear(self):
204        """any: The value at rear if the circular queue is not empty. Otherwise None."""
205        return self.array[self.rear_idx] if not self.is_empty() else None

any: The value at rear if the circular queue is not empty. Otherwise None.

def enqueue(self, value):
253    def enqueue(self, value):
254        """Enqueue data for a circular queue.
255
256        This is a manual implementation instead of using %. Each time a new insertion is
257        made, the front_idx and rear_idx changes.
258
259        Args:
260            value (any): To enqueue inside the CircularQueue.
261
262        Raises:
263            Exception: If the CircularQueue is full.
264        """
265        if self.is_full():
266            raise Exception("The circular queue is full")
267        elif self.is_empty():
268            self.front_idx = self.rear_idx = 0
269        elif self.is_rear_idx_reached() and self.front_idx != 0:
270            self.rear_idx = 0
271        else:
272            self.rear_idx = self.rear_idx + 1
273        self.array[self.rear_idx] = value
274        self._size += 1

Enqueue data for a circular queue.

This is a manual implementation instead of using %. Each time a new insertion is made, the front_idx and rear_idx changes.

Arguments:
  • value (any): To enqueue inside the CircularQueue.
Raises:
  • Exception: If the CircularQueue is full.
def dequeue(self):
276    def dequeue(self):
277        """Dequeue an element from the CircularQueue.
278
279        Using the modular arithmetics, front and rear indices are updated.
280
281        Raises:
282            Exception: If the circular queue is empty.
283        """
284        if self.is_empty():
285            raise Exception("The circular queue is empty")
286        value = self.array[self.front_idx]
287        self.array[self.front_idx] = None
288        self.front_idx = (self.front_idx + 1) % self.capacity
289        self._size -= 1
290        if self._size == 0:
291            self.front_idx = -1
292            self.rear_idx = -1
293        return value

Dequeue an element from the CircularQueue.

Using the modular arithmetics, front and rear indices are updated.

Raises:
  • Exception: If the circular queue is empty.
def is_empty(self):
295    def is_empty(self):
296        """bool: Return True if the size is 0, else False."""
297        return self.size == 0

bool: Return True if the size is 0, else False.

def is_full(self):
299    def is_full(self):
300        """bool: Return True if the queue is full capacity, else False."""
301        return (self.rear_idx + 1) % self.capacity == self.front_idx

bool: Return True if the queue is full capacity, else False.

def is_rear_idx_reached(self):
303    def is_rear_idx_reached(self):
304        """bool: Return True if the `rear_idx` reached at the end, else False.
305
306        This is used to check if the rear_idx should be jumped to the front of the circular queue.
307        """
308        return self.rear_idx == self.capacity - 1

bool: Return True if the rear_idx reached at the end, else False.

This is used to check if the rear_idx should be jumped to the front of the circular queue.

def clear(self):
310    def clear(self):
311        """Clears the whole CircularQueue object.
312
313        Creates the default array representation. Reset the front and rear indices, set the size to 0.
314        """
315        self.array = [None] * self.capacity
316        self.front_idx = -1
317        self.rear_idx = -1
318        self._size = 0

Clears the whole CircularQueue object.

Creates the default array representation. Reset the front and rear indices, set the size to 0.

def copy(self):
320    def copy(self):
321        """Return a deep copy of the CircularQueue object."""
322        return CircularQueue(*self, capacity=self.capacity)

Return a deep copy of the CircularQueue object.

class PriorityQueue(data_structures.LinkedList):
325class PriorityQueue(LinkedList):
326    """Implementation of priority queue, where queue is made up of linked lists with priority levels.
327
328    Attributes:
329        value (Any): Value of the node.
330    """
331
332    def __init__(self, value=None):
333        """Initialize a PriorityQueue object.
334
335        This is used a specialized node class called `NodePriorityQueue`.
336
337        Args:
338            value (Any): Value of the node.
339        """
340        super().__init__(value, node=NodePriorityQueue)
341
342    @property
343    def front(self):
344        """Return the value at the front of the queue, or None if empty."""
345        return self.head_node.value if not self.is_empty() else None
346
347    @property
348    def rear(self):
349        """any: The value at front (tail node's value of the linked list) if the queue is not empty. Otherwise None."""
350        return self.tail_node.value if not self.is_empty() else None
351
352    def enqueue(self, value, priority):
353        """Enqueue the element inside the linked queue.
354
355        First, a value is wrapped inside the node object. If there's a node with a higher priority,
356        it's inserted to front. The nodes with the same priority level are behaved like a regular queue (FIFO).
357
358        Args:
359            value (any): Value to be enqueued.
360            priority (int): Priority level
361        """
362        new_node = self._Node(value, priority)
363        current_node = self.head_node
364        prev_node = None
365
366        while current_node is not None:
367            if new_node.priority > current_node.priority:
368                if prev_node is None:
369                    self._insert_node_head(new_node)
370                    return
371                else:
372                    prev_node.next_node = new_node
373                    new_node.next_node = current_node
374                    self._size += 1
375                return
376            elif new_node.priority == current_node.priority:
377                new_node.next_node = current_node.next_node
378                current_node.next_node = new_node
379                self._size += 1
380                return
381            prev_node = current_node
382            current_node = current_node.next_node
383
384        if prev_node is not None:
385            prev_node.next_node = new_node
386            self.tail_node = new_node
387        else:
388            self.head_node = new_node
389        self._size += 1
390
391    def dequeue(self):
392        """Dequeue the element (remove the head node from the list).
393
394        Raises:
395            Exception: If the priority queue is empty.
396        """
397        if self.size == 0:
398            raise Exception("The priority queue is empty")
399        value = self.head_node.value
400        self.delete_head()
401        return value
402
403    def _insert_node_head(self, new_node):
404        new_node.next_node = self.head_node
405        self.head_node = new_node
406        self._size += 1
407        if self._size == 1:
408            self.tail_node = new_node
409
410    def is_empty(self):
411        """bool: Return True if the size is 0, else False."""
412        return self.size == 0
413
414    def clear(self):
415        """Clear the whole PriorityQueue object."""
416        while not self.is_empty():
417            self.delete_head()
418
419    def copy(self):
420        """Return a deep copy of the current PriorityQueue object."""
421        new_queue = PriorityQueue()
422        current_node = self.head_node
423        while current_node is not None:
424            new_queue.enqueue(current_node.value, current_node.priority)
425            current_node = current_node.next_node
426        return new_queue

Implementation of priority queue, where queue is made up of linked lists with priority levels.

Attributes:
  • value (Any): Value of the node.
PriorityQueue(value=None)
332    def __init__(self, value=None):
333        """Initialize a PriorityQueue object.
334
335        This is used a specialized node class called `NodePriorityQueue`.
336
337        Args:
338            value (Any): Value of the node.
339        """
340        super().__init__(value, node=NodePriorityQueue)

Initialize a PriorityQueue object.

This is used a specialized node class called NodePriorityQueue.

Arguments:
  • value (Any): Value of the node.
front
342    @property
343    def front(self):
344        """Return the value at the front of the queue, or None if empty."""
345        return self.head_node.value if not self.is_empty() else None

Return the value at the front of the queue, or None if empty.

rear
347    @property
348    def rear(self):
349        """any: The value at front (tail node's value of the linked list) if the queue is not empty. Otherwise None."""
350        return self.tail_node.value if not self.is_empty() else None

any: The value at front (tail node's value of the linked list) if the queue is not empty. Otherwise None.

def enqueue(self, value, priority):
352    def enqueue(self, value, priority):
353        """Enqueue the element inside the linked queue.
354
355        First, a value is wrapped inside the node object. If there's a node with a higher priority,
356        it's inserted to front. The nodes with the same priority level are behaved like a regular queue (FIFO).
357
358        Args:
359            value (any): Value to be enqueued.
360            priority (int): Priority level
361        """
362        new_node = self._Node(value, priority)
363        current_node = self.head_node
364        prev_node = None
365
366        while current_node is not None:
367            if new_node.priority > current_node.priority:
368                if prev_node is None:
369                    self._insert_node_head(new_node)
370                    return
371                else:
372                    prev_node.next_node = new_node
373                    new_node.next_node = current_node
374                    self._size += 1
375                return
376            elif new_node.priority == current_node.priority:
377                new_node.next_node = current_node.next_node
378                current_node.next_node = new_node
379                self._size += 1
380                return
381            prev_node = current_node
382            current_node = current_node.next_node
383
384        if prev_node is not None:
385            prev_node.next_node = new_node
386            self.tail_node = new_node
387        else:
388            self.head_node = new_node
389        self._size += 1

Enqueue the element inside the linked queue.

First, a value is wrapped inside the node object. If there's a node with a higher priority, it's inserted to front. The nodes with the same priority level are behaved like a regular queue (FIFO).

Arguments:
  • value (any): Value to be enqueued.
  • priority (int): Priority level
def dequeue(self):
391    def dequeue(self):
392        """Dequeue the element (remove the head node from the list).
393
394        Raises:
395            Exception: If the priority queue is empty.
396        """
397        if self.size == 0:
398            raise Exception("The priority queue is empty")
399        value = self.head_node.value
400        self.delete_head()
401        return value

Dequeue the element (remove the head node from the list).

Raises:
  • Exception: If the priority queue is empty.
def is_empty(self):
410    def is_empty(self):
411        """bool: Return True if the size is 0, else False."""
412        return self.size == 0

bool: Return True if the size is 0, else False.

def clear(self):
414    def clear(self):
415        """Clear the whole PriorityQueue object."""
416        while not self.is_empty():
417            self.delete_head()

Clear the whole PriorityQueue object.

def copy(self):
419    def copy(self):
420        """Return a deep copy of the current PriorityQueue object."""
421        new_queue = PriorityQueue()
422        current_node = self.head_node
423        while current_node is not None:
424            new_queue.enqueue(current_node.value, current_node.priority)
425            current_node = current_node.next_node
426        return new_queue

Return a deep copy of the current PriorityQueue object.

class StackArray:
  7class StackArray:
  8    """Stack data structure using an array.
  9
 10    Attributes:
 11        array (List): Empty array. This will contain the data.
 12    """
 13
 14    def __init__(self, *args):
 15        """Initialize a `StackArray` object.
 16
 17        Args:
 18            *args: Variable length argument list. Those are basically the elements to be stored.
 19        """
 20        self.array = []
 21        for arg in args:
 22            self.push(arg)
 23
 24    @property
 25    def size(self):
 26        """int: Size of the array."""
 27        return len(self.array)
 28
 29    @property
 30    def top(self):
 31        """any: The value at the top. Otherwise None."""
 32        return self.array[-1] if not self.is_empty() else None
 33
 34    def __str__(self):
 35        """str: String representation of `StackArray` object."""
 36        return "[ " + " ".join(str(element) for element in reversed(self.array)) + " ]"
 37
 38    def __len__(self):
 39        """int: Return the size of the stack."""
 40        return self.size
 41
 42    def __iter__(self):
 43        """Dunder method to make `StackArray` object iterable."""
 44        return reversed(self.array)
 45
 46    def __contains__(self, value):
 47        """Dunder method to have a more pythonic experience.
 48
 49        Args:
 50            value (any): The value to be pushed into the stack.
 51
 52        Example:
 53            >>> stack = StackArray(1, 2, 3)
 54            >>> if 3 in stack:
 55            >>>     print("3 is inside the stack")
 56        """
 57        return value in self.array
 58
 59    def push(self, value):
 60        """Push an element into the stack.
 61
 62        Args:
 63            value (any): Value to be inserted.
 64        """
 65        self.array.append(value)
 66
 67    def pop(self):
 68        """Pop an element from the stack.
 69
 70        Raises:
 71            IndexError: If the stack is empty.
 72        """
 73        if self.size == 0:
 74            raise IndexError("The stack is empty")
 75        else:
 76            value = self.array.pop()
 77            return value
 78
 79    def peek(self):
 80        """Return the top element without popping the element.
 81
 82        Raises:
 83            IndexError: If the stack is empty.
 84        """
 85        if self.size == 0:
 86            raise IndexError("The stack is empty")
 87        else:
 88            return self.top
 89
 90    def is_empty(self):
 91        """bool: Return True if the size is 0. Otherwise False."""
 92        return self.size == 0
 93
 94    def clear(self):
 95        """Clear the whole `StackArray` object."""
 96        self.array.clear()
 97
 98    def copy(self):
 99        """Return a deep copy of the `StackArray` object.
100
101        The `*self.array` unpacks elements into a new stack.
102        """
103        return StackArray(*self.array)

Stack data structure using an array.

Attributes:
  • array (List): Empty array. This will contain the data.
StackArray(*args)
14    def __init__(self, *args):
15        """Initialize a `StackArray` object.
16
17        Args:
18            *args: Variable length argument list. Those are basically the elements to be stored.
19        """
20        self.array = []
21        for arg in args:
22            self.push(arg)

Initialize a StackArray object.

Arguments:
  • *args: Variable length argument list. Those are basically the elements to be stored.
array
size
24    @property
25    def size(self):
26        """int: Size of the array."""
27        return len(self.array)

int: Size of the array.

top
29    @property
30    def top(self):
31        """any: The value at the top. Otherwise None."""
32        return self.array[-1] if not self.is_empty() else None

any: The value at the top. Otherwise None.

def push(self, value):
59    def push(self, value):
60        """Push an element into the stack.
61
62        Args:
63            value (any): Value to be inserted.
64        """
65        self.array.append(value)

Push an element into the stack.

Arguments:
  • value (any): Value to be inserted.
def pop(self):
67    def pop(self):
68        """Pop an element from the stack.
69
70        Raises:
71            IndexError: If the stack is empty.
72        """
73        if self.size == 0:
74            raise IndexError("The stack is empty")
75        else:
76            value = self.array.pop()
77            return value

Pop an element from the stack.

Raises:
  • IndexError: If the stack is empty.
def peek(self):
79    def peek(self):
80        """Return the top element without popping the element.
81
82        Raises:
83            IndexError: If the stack is empty.
84        """
85        if self.size == 0:
86            raise IndexError("The stack is empty")
87        else:
88            return self.top

Return the top element without popping the element.

Raises:
  • IndexError: If the stack is empty.
def is_empty(self):
90    def is_empty(self):
91        """bool: Return True if the size is 0. Otherwise False."""
92        return self.size == 0

bool: Return True if the size is 0. Otherwise False.

def clear(self):
94    def clear(self):
95        """Clear the whole `StackArray` object."""
96        self.array.clear()

Clear the whole StackArray object.

def copy(self):
 98    def copy(self):
 99        """Return a deep copy of the `StackArray` object.
100
101        The `*self.array` unpacks elements into a new stack.
102        """
103        return StackArray(*self.array)

Return a deep copy of the StackArray object.

The *self.array unpacks elements into a new stack.

class LinkedStack(data_structures.LinkedList):
106class LinkedStack(LinkedList):
107    """This is the stack implementation using linked lists. It has all the functionality a `LinkedList` has."""
108
109    def __init__(self, *values):
110        """Initialize a `LinkedStack` object.
111
112        Args:
113            *values: Variable length argument list. Those are basically the elements to be stored.
114        """
115        super().__init__()
116        for value in values:
117            self.insert_head(value)
118
119    @property
120    def top(self):
121        """any: The value at the top. Otherwise None."""
122        return self.head_node.value if self.head_node else None
123
124    def push(self, value):
125        """Push an element into the stack.
126
127        Since it's using a linked list, this method is inserting the value to the head of the list.
128
129        Args:
130            value (any): Value to be inserted.
131        """
132        self.insert_head(value)
133
134    def pop(self):
135        """Pop an element from the stack.
136
137        Raises:
138            Exception: If the stack is empty.
139        """
140        if self.size > 0:
141            element = self.head_node.value
142            self.delete_value(self.head_node.value)
143            return element
144        else:
145            raise Exception("The stack list is empty")
146
147    def peek(self):
148        """Return the top element without popping the element.
149
150        Raises:
151            IndexError: If the stack is empty.
152        """
153        if self.size == 0:
154            raise IndexError("The stack is empty")
155        else:
156            return self.top
157
158    def is_empty(self):
159        """bool: Return True if the size is 0. Otherwise False."""
160        return self.size == 0
161
162    def clear(self):
163        """Clear the whole `LinkedArray` object."""
164        while self.size != 0:
165            self.delete_head()
166
167    def copy(self):
168        """Return a deep copy of the `LinkedStack` object."""
169        return LinkedStack(*reversed(list(self)))

This is the stack implementation using linked lists. It has all the functionality a LinkedList has.

LinkedStack(*values)
109    def __init__(self, *values):
110        """Initialize a `LinkedStack` object.
111
112        Args:
113            *values: Variable length argument list. Those are basically the elements to be stored.
114        """
115        super().__init__()
116        for value in values:
117            self.insert_head(value)

Initialize a LinkedStack object.

Arguments:
  • *values: Variable length argument list. Those are basically the elements to be stored.
top
119    @property
120    def top(self):
121        """any: The value at the top. Otherwise None."""
122        return self.head_node.value if self.head_node else None

any: The value at the top. Otherwise None.

def push(self, value):
124    def push(self, value):
125        """Push an element into the stack.
126
127        Since it's using a linked list, this method is inserting the value to the head of the list.
128
129        Args:
130            value (any): Value to be inserted.
131        """
132        self.insert_head(value)

Push an element into the stack.

Since it's using a linked list, this method is inserting the value to the head of the list.

Arguments:
  • value (any): Value to be inserted.
def pop(self):
134    def pop(self):
135        """Pop an element from the stack.
136
137        Raises:
138            Exception: If the stack is empty.
139        """
140        if self.size > 0:
141            element = self.head_node.value
142            self.delete_value(self.head_node.value)
143            return element
144        else:
145            raise Exception("The stack list is empty")

Pop an element from the stack.

Raises:
  • Exception: If the stack is empty.
def peek(self):
147    def peek(self):
148        """Return the top element without popping the element.
149
150        Raises:
151            IndexError: If the stack is empty.
152        """
153        if self.size == 0:
154            raise IndexError("The stack is empty")
155        else:
156            return self.top

Return the top element without popping the element.

Raises:
  • IndexError: If the stack is empty.
def is_empty(self):
158    def is_empty(self):
159        """bool: Return True if the size is 0. Otherwise False."""
160        return self.size == 0

bool: Return True if the size is 0. Otherwise False.

def clear(self):
162    def clear(self):
163        """Clear the whole `LinkedArray` object."""
164        while self.size != 0:
165            self.delete_head()

Clear the whole LinkedArray object.

def copy(self):
167    def copy(self):
168        """Return a deep copy of the `LinkedStack` object."""
169        return LinkedStack(*reversed(list(self)))

Return a deep copy of the LinkedStack object.

traversal_keywords = ['print', 'list', 'generator']
copy_keywords = ['iterative', 'recursive']
default_keyword = 'list'
class BinarySearchTree:
 10class BinarySearchTree:
 11    def __init__(self, value=None, node=TreeNode):
 12        self._Node = node
 13        if value is None:
 14            self._root = None
 15            self._size = 0
 16        else:
 17            self._root = self._Node(value)
 18            self._size = 1
 19
 20    @property
 21    def root(self):
 22        return self._root
 23
 24    @root.setter
 25    def root(self, value):
 26        self._root = value
 27
 28    @property
 29    def min(self):
 30        return self.find_min().value
 31
 32    @property
 33    def max(self):
 34        return self.find_max().value
 35
 36    @property
 37    def size(self):
 38        return self._size
 39
 40    @property
 41    def is_empty(self):
 42        return self.root is None
 43
 44    def __len__(self):
 45        return self._size
 46
 47    def __iter__(self):
 48        for node in self._inorder(self.root):
 49            yield node
 50
 51    def __contains__(self, value):
 52        node = self.search_element(value)
 53        if node is not None:
 54            return True
 55        else:
 56            return False
 57
 58    def _check_empty(self):
 59        if self.root is None:
 60            raise Exception("Binary search tree is empty")
 61
 62    def _check_param_type(self, param_type, keyword_list):
 63        if not isinstance(param_type, str):
 64            raise TypeError(
 65                f"Invalid type: {type(param_type)} for argument. Enter relevant 'str'"
 66                + "keywords from the list {keyword_list}"
 67            )
 68        elif param_type not in keyword_list:
 69            raise Exception(
 70                f"Invalid keyword. Choose one in between the list: {keyword_list}"
 71            )
 72
 73    def inorder_traversal(self, output_type=default_keyword):
 74        self._check_param_type(output_type, traversal_keywords)
 75        self._check_empty()
 76
 77        if output_type == "list":
 78            return list(node.value for node in self._inorder(self.root))
 79        elif output_type == "print":
 80            for i in self._inorder(self.root):
 81                print(i.value)
 82        elif output_type == "generator":
 83            return self._inorder(self.root)
 84
 85    def _inorder(self, node):
 86        if node is None:
 87            return
 88        yield from self._inorder(node.left_child)
 89        yield node
 90        yield from self._inorder(node.right_child)
 91
 92    def preorder_traversal(self, output_type=default_keyword):
 93        self._check_param_type(output_type, traversal_keywords)
 94        self._check_empty()
 95
 96        if output_type == "list":
 97            return list(node.value for node in self._preorder(self.root))
 98        elif output_type == "print":
 99            for i in self._preorder(self.root):
100                print(i.value)
101        elif output_type == "generator":
102            return self._preorder(self.root)
103
104    def _preorder(self, node):
105        if node is None:
106            return
107        yield node
108        yield from self._preorder(node.left_child)
109        yield from self._preorder(node.right_child)
110
111    def postorder_traversal(self, output_type=default_keyword):
112        self._check_param_type(output_type, traversal_keywords)
113        self._check_empty()
114
115        if output_type == "list":
116            return list(node.value for node in self._postorder(self.root))
117        elif output_type == "print":
118            for i in self._postorder(self.root):
119                print(i.value)
120        elif output_type == "generator":
121            return self._postorder(self.root)
122
123    def _postorder(self, node):
124        if node is None:
125            return
126        yield from self._postorder(node.left_child)
127        yield from self._postorder(node.right_child)
128        yield node
129
130    def levelorder_traversal(self, none_value=None):
131        if self.root is none_value:
132            return none_value
133        queue = QueueArray(self.root)
134        result = []
135        while queue.size != 0:
136            current_node = queue.dequeue()
137            result.append(current_node.value)
138            if current_node.left_child is not none_value:
139                queue.enqueue(current_node.left_child)
140            if current_node.right_child is not none_value:
141                queue.enqueue(current_node.right_child)
142        return result
143
144    def insert(self, data, node=None):
145        if not isinstance(data, self._Node):
146            new_node = self._Node(data)
147        else:
148            new_node = data
149
150        if self._root is None:
151            self.root = new_node
152            self._size += 1
153            return new_node
154        else:
155            current_node = self.root if node is None else node
156            if new_node.value >= current_node.value:
157                if current_node.right_child is None:
158                    current_node.right_child = new_node
159                    new_node.parent = current_node
160                    self._size += 1
161                    return new_node
162                else:
163                    return self.insert(new_node, current_node.right_child)
164            else:
165                if current_node.left_child is None:
166                    current_node.left_child = new_node
167                    new_node.parent = current_node
168                    self._size += 1
169                    return new_node
170                else:
171                    return self.insert(new_node, current_node.left_child)
172    
173    def insert_iterative(self, data):
174        if not isinstance(data, self._Node):
175            new_node = self._Node(data)
176        else:
177            new_node = data
178
179        if self.root is None:
180            self.root = new_node
181            self._size += 1
182            return new_node
183        else:
184            current_node = self.root
185            while True:
186                if new_node.value >= current_node.value:
187                    if current_node.right_child is None:
188                        current_node.right_child = new_node
189                        new_node.parent = current_node
190                        self._size += 1
191                        return new_node
192                    current_node = current_node.right_child
193                elif new_node.value < current_node.value:
194                    if current_node.left_child is None:
195                        current_node.left_child = new_node
196                        new_node.parent = current_node
197                        self._size += 1
198                        return new_node
199                    current_node = current_node.left_child
200        
201    def search_element(self, value, node=None):
202        if not isinstance(value, self._Node):
203            searched_node = self._Node(value)
204        else:
205            searched_node = value
206        current_node = self.root if node is None else node
207
208        if self._root is None:
209            raise Exception("Binary search tree is empty")
210        elif current_node.value == searched_node.value:
211            return current_node
212        else:
213            if searched_node.value < current_node.value:
214                if current_node.left_child is None:
215                    return None
216                return self.search_element(searched_node, current_node.left_child)
217            else:
218                if current_node.right_child is None:
219                    return None
220                return self.search_element(searched_node, current_node.right_child)
221
222    def search_iterative(self, value, none_value=None):
223        if self._root is none_value:
224            raise Exception("Binary search tree is empty")        
225        current_node = self.root
226        while current_node is not none_value:
227            if current_node.value == value:
228                return current_node
229            elif current_node.value >= value:
230                current_node = current_node.left_child
231            else:
232                current_node = current_node.right_child
233        raise Exception(f"Value {value} is not found")
234        
235    def delete_node(self, value, node=None):
236        if node is None:
237            node = self.search_element(value, node=node) # returns None if element couldn't be found
238        if node is None:
239            raise Exception(f"{value} does not exist in the tree")
240
241        parent_node = node.parent
242        # deleting a node that has no children
243        if node.left_child is None and node.right_child is None:
244            if parent_node.left_child == node:
245                parent_node.left_child = None
246            elif parent_node.right_child == node:
247                parent_node.right_child = None
248            self._size -= 1
249        # deleting a node that has only one child
250        elif node.left_child is None and node.right_child is not None:
251            if parent_node.left_child == node:
252                parent_node.left_child = node.right_child
253            else:
254                parent_node.right_child = node.right_child
255            self._size -= 1
256        elif node.left_child is not None and node.right_child is None:
257            if parent_node.left_child == node:
258                parent_node.left_child = node.left_child
259            else:
260                parent_node.right_child = node.left_child
261            self._size -= 1
262        # deleting a node with two children (using in order successor)
263        else:
264            in_order_successor = node.right_child
265            while in_order_successor.left_child:
266                in_order_successor = in_order_successor.left_child
267            node.value = in_order_successor.value
268            # now there are two nodes with the same value. when self.delete() called,       \
269            # as a default value None, the search_element() method will start looking       \
270            # by the root node. every time this will end up in the first occurence of the   \
271            # same value. to avoid this, we can start from node.right_child but we already  \
272            # have access to the real in_order_successor, so the function below in fact     \
273            # will take O(1) time.
274            self.delete_node(in_order_successor.value, in_order_successor)
275
276    def delete_iterative(self, value):
277        node = self._root
278        parent_node = None
279        
280        while node is not None and node.value != value:
281            parent_node = node
282            if node.value > value:
283                node = node.left_child
284            else:
285                node = node.right_child
286        
287        if node is None:
288            raise Exception(f"{value} is not found in the tree")
289        
290        # deleting a node that has no children
291        if node.left_child is None and node.right_child is None:
292            if parent_node is None:
293                self.root = None
294            elif parent_node.left_child == node:
295                parent_node.left_child = None
296            elif parent_node.right_child == node:
297                parent_node.right_child = None
298            self._size -= 1
299        # deleting a node that has only one child
300        elif node.left_child is None or node.right_child is None:
301            child_node = node.left_child if node.left_child else node.right_child
302            if parent_node is None:
303                self.root = child_node
304            elif parent_node.left_child == node:
305                parent_node.left_child = child_node
306            else:
307                parent_node.right_child = child_node
308            if child_node:
309                child_node.parent = parent_node
310        # deleting a node with two children (using in order successor)
311        else:
312            successor_parent = node
313            successor = node.right_child
314            while successor.left_child is not None:
315                successor_parent = successor
316                successor = successor.left_child
317            
318            node.value = successor.value
319            if successor_parent.left_child == successor:
320                successor_parent.left_child = successor.right_child
321            else:
322                successor_parent.right_child = successor.right_child
323            
324            if successor.right_child:  # Update parent if successor had a right child
325                successor.right_child.parent = successor_parent
326            self._size -= 1
327            
328    def find_min(self, node=None, return_value=False, none_value=None):
329        self._check_empty()
330        if node is none_value:
331            current_node = self.root
332        else:
333            current_node = node
334            
335        while current_node.left_child is not none_value:
336            current_node = current_node.left_child
337        
338        if return_value is True:
339            return current_node.value
340        return current_node
341
342    def find_max(self, node=None, return_value=False, none_value=None):
343        self._check_empty()
344        if node is none_value:
345            current_node = self.root
346        else:
347            current_node = node
348            
349        while current_node.right_child is not none_value:
350            current_node = current_node.right_child
351        if return_value is True:
352            return current_node.value
353        return current_node
354
355    def height(self, opt_node=_sentinel):
356        if opt_node is _sentinel:
357            return self.height(self.root)
358        if opt_node is None:
359            return 0
360        else:
361            left_height = self.height(opt_node.left_child)
362            right_height = self.height(opt_node.right_child)
363            return max(left_height, right_height) + 1
364
365    def get_size(self):
366        if self.root is None:
367            return 0
368
369        def _size(node=None):
370            if node is None:
371                return 0
372            return _size(node.left_child) + _size(node.right_child) + 1
373
374        ### to be removed later
375        if _size(self.root) != self._size:
376            print("get_size() fonksiyonundan dönüldü. self_size'da hata olmalı")
377            return
378        ###
379        return _size(self.root)
380
381    def clear(self):
382        self.root = None
383        self._size = 0
384
385    def copy(self, method="recursive"):
386        self._check_empty()
387        self._check_param_type(method, copy_keywords)
388        tree = BinarySearchTree()
389
390        def _copy_iterative():
391            for node in self._preorder(self.root):
392                tree.insert(node.value)
393            return tree
394
395        def _copy_recursive(node, parent):
396            if node is None:
397                return None
398            new_node = self._Node(node.value)
399            new_node.parent = parent
400            tree._size += 1
401            new_node.left_child = _copy_recursive(node.left_child, new_node)
402            new_node.right_child = _copy_recursive(node.right_child, new_node)
403            return new_node
404
405        if method == "iterative":
406            return _copy_iterative()
407        else:
408            tree.root = _copy_recursive(self.root, self.root.parent)
409            return tree
410
411    def is_balanced(self, node=None):
412        if node is None:
413            node = self.root
414        if not isinstance(node, TreeNode):
415            raise Exception(f"Please provide a {TreeNode} object")
416        left_subtree_height = self.height(node.left_child)
417        right_subtree_height = self.height(node.right_child)
418        return False if abs(left_subtree_height - right_subtree_height) > 1 else True
419
420    def pretty_print(self, none_value=None):
421        """Prints the tree in a visually appealing way in the terminal."""
422        if self._root is none_value:
423            print("(empty tree)")
424            return
425        
426        lines, *_ = self._pretty_print_helper(self._root, none_value)
427        for line in lines:
428            print(line)
429
430    def _pretty_print_helper(self, node, none_value=None):
431        """Returns list of strings, width, height, and horizontal coordinate of the root."""
432        if node._right_child is none_value and node._left_child is none_value:
433            line = str(node._value)
434            width = len(line)
435            height = 1
436            middle = width // 2
437            return [line], width, height, middle
438
439        # Only left child
440        if node._right_child is none_value:
441            lines, n, p, x = self._pretty_print_helper(node._left_child, none_value)
442            s = str(node._value)
443            u = len(s)
444            first_line = (x + 1) * ' ' + (n - x - 1) * '_' + s
445            second_line = x * ' ' + '/' + (n - x - 1 + u) * ' '
446            shifted_lines = [line + u * ' ' for line in lines]
447            return [first_line, second_line] + shifted_lines, n + u, p + 2, n + u // 2
448
449        # Only right child
450        if node._left_child is none_value:
451            lines, n, p, x = self._pretty_print_helper(node._right_child, none_value)
452            s = str(node._value)
453            u = len(s)
454            first_line = s + x * '_' + (n - x) * ' '
455            second_line = (u + x) * ' ' + '\\' + (n - x - 1) * ' '
456            shifted_lines = [u * ' ' + line for line in lines]
457            return [first_line, second_line] + shifted_lines, n + u, p + 2, u // 2
458
459        # Two children
460        left, n, p, x = self._pretty_print_helper(node._left_child, none_value)
461        right, m, q, y = self._pretty_print_helper(node._right_child, none_value)
462        s = str(node._value)
463        u = len(s)
464        first_line = (x + 1) * ' ' + (n - x - 1) * '_' + s + y * '_' + (m - y) * ' '
465        second_line = x * ' ' + '/' + (n - x - 1 + u + y) * ' ' + '\\' + (m - y - 1) * ' '
466        if p < q:
467            left += [n * ' '] * (q - p)
468        elif q < p:
469            right += [m * ' '] * (p - q)
470        zipped_lines = zip(left, right)
471        lines = [first_line, second_line] + [a + u * ' ' + b for a, b in zipped_lines]
472        return lines, n + m + u, max(p, q) + 2, n + u // 2
BinarySearchTree(value=None, node=<class 'TreeNode'>)
11    def __init__(self, value=None, node=TreeNode):
12        self._Node = node
13        if value is None:
14            self._root = None
15            self._size = 0
16        else:
17            self._root = self._Node(value)
18            self._size = 1
root
20    @property
21    def root(self):
22        return self._root
min
28    @property
29    def min(self):
30        return self.find_min().value
max
32    @property
33    def max(self):
34        return self.find_max().value
size
36    @property
37    def size(self):
38        return self._size
is_empty
40    @property
41    def is_empty(self):
42        return self.root is None
def inorder_traversal(self, output_type='list'):
73    def inorder_traversal(self, output_type=default_keyword):
74        self._check_param_type(output_type, traversal_keywords)
75        self._check_empty()
76
77        if output_type == "list":
78            return list(node.value for node in self._inorder(self.root))
79        elif output_type == "print":
80            for i in self._inorder(self.root):
81                print(i.value)
82        elif output_type == "generator":
83            return self._inorder(self.root)
def preorder_traversal(self, output_type='list'):
 92    def preorder_traversal(self, output_type=default_keyword):
 93        self._check_param_type(output_type, traversal_keywords)
 94        self._check_empty()
 95
 96        if output_type == "list":
 97            return list(node.value for node in self._preorder(self.root))
 98        elif output_type == "print":
 99            for i in self._preorder(self.root):
100                print(i.value)
101        elif output_type == "generator":
102            return self._preorder(self.root)
def postorder_traversal(self, output_type='list'):
111    def postorder_traversal(self, output_type=default_keyword):
112        self._check_param_type(output_type, traversal_keywords)
113        self._check_empty()
114
115        if output_type == "list":
116            return list(node.value for node in self._postorder(self.root))
117        elif output_type == "print":
118            for i in self._postorder(self.root):
119                print(i.value)
120        elif output_type == "generator":
121            return self._postorder(self.root)
def levelorder_traversal(self, none_value=None):
130    def levelorder_traversal(self, none_value=None):
131        if self.root is none_value:
132            return none_value
133        queue = QueueArray(self.root)
134        result = []
135        while queue.size != 0:
136            current_node = queue.dequeue()
137            result.append(current_node.value)
138            if current_node.left_child is not none_value:
139                queue.enqueue(current_node.left_child)
140            if current_node.right_child is not none_value:
141                queue.enqueue(current_node.right_child)
142        return result
def insert(self, data, node=None):
144    def insert(self, data, node=None):
145        if not isinstance(data, self._Node):
146            new_node = self._Node(data)
147        else:
148            new_node = data
149
150        if self._root is None:
151            self.root = new_node
152            self._size += 1
153            return new_node
154        else:
155            current_node = self.root if node is None else node
156            if new_node.value >= current_node.value:
157                if current_node.right_child is None:
158                    current_node.right_child = new_node
159                    new_node.parent = current_node
160                    self._size += 1
161                    return new_node
162                else:
163                    return self.insert(new_node, current_node.right_child)
164            else:
165                if current_node.left_child is None:
166                    current_node.left_child = new_node
167                    new_node.parent = current_node
168                    self._size += 1
169                    return new_node
170                else:
171                    return self.insert(new_node, current_node.left_child)
def insert_iterative(self, data):
173    def insert_iterative(self, data):
174        if not isinstance(data, self._Node):
175            new_node = self._Node(data)
176        else:
177            new_node = data
178
179        if self.root is None:
180            self.root = new_node
181            self._size += 1
182            return new_node
183        else:
184            current_node = self.root
185            while True:
186                if new_node.value >= current_node.value:
187                    if current_node.right_child is None:
188                        current_node.right_child = new_node
189                        new_node.parent = current_node
190                        self._size += 1
191                        return new_node
192                    current_node = current_node.right_child
193                elif new_node.value < current_node.value:
194                    if current_node.left_child is None:
195                        current_node.left_child = new_node
196                        new_node.parent = current_node
197                        self._size += 1
198                        return new_node
199                    current_node = current_node.left_child
def search_element(self, value, node=None):
201    def search_element(self, value, node=None):
202        if not isinstance(value, self._Node):
203            searched_node = self._Node(value)
204        else:
205            searched_node = value
206        current_node = self.root if node is None else node
207
208        if self._root is None:
209            raise Exception("Binary search tree is empty")
210        elif current_node.value == searched_node.value:
211            return current_node
212        else:
213            if searched_node.value < current_node.value:
214                if current_node.left_child is None:
215                    return None
216                return self.search_element(searched_node, current_node.left_child)
217            else:
218                if current_node.right_child is None:
219                    return None
220                return self.search_element(searched_node, current_node.right_child)
def search_iterative(self, value, none_value=None):
222    def search_iterative(self, value, none_value=None):
223        if self._root is none_value:
224            raise Exception("Binary search tree is empty")        
225        current_node = self.root
226        while current_node is not none_value:
227            if current_node.value == value:
228                return current_node
229            elif current_node.value >= value:
230                current_node = current_node.left_child
231            else:
232                current_node = current_node.right_child
233        raise Exception(f"Value {value} is not found")
def delete_node(self, value, node=None):
235    def delete_node(self, value, node=None):
236        if node is None:
237            node = self.search_element(value, node=node) # returns None if element couldn't be found
238        if node is None:
239            raise Exception(f"{value} does not exist in the tree")
240
241        parent_node = node.parent
242        # deleting a node that has no children
243        if node.left_child is None and node.right_child is None:
244            if parent_node.left_child == node:
245                parent_node.left_child = None
246            elif parent_node.right_child == node:
247                parent_node.right_child = None
248            self._size -= 1
249        # deleting a node that has only one child
250        elif node.left_child is None and node.right_child is not None:
251            if parent_node.left_child == node:
252                parent_node.left_child = node.right_child
253            else:
254                parent_node.right_child = node.right_child
255            self._size -= 1
256        elif node.left_child is not None and node.right_child is None:
257            if parent_node.left_child == node:
258                parent_node.left_child = node.left_child
259            else:
260                parent_node.right_child = node.left_child
261            self._size -= 1
262        # deleting a node with two children (using in order successor)
263        else:
264            in_order_successor = node.right_child
265            while in_order_successor.left_child:
266                in_order_successor = in_order_successor.left_child
267            node.value = in_order_successor.value
268            # now there are two nodes with the same value. when self.delete() called,       \
269            # as a default value None, the search_element() method will start looking       \
270            # by the root node. every time this will end up in the first occurence of the   \
271            # same value. to avoid this, we can start from node.right_child but we already  \
272            # have access to the real in_order_successor, so the function below in fact     \
273            # will take O(1) time.
274            self.delete_node(in_order_successor.value, in_order_successor)
def delete_iterative(self, value):
276    def delete_iterative(self, value):
277        node = self._root
278        parent_node = None
279        
280        while node is not None and node.value != value:
281            parent_node = node
282            if node.value > value:
283                node = node.left_child
284            else:
285                node = node.right_child
286        
287        if node is None:
288            raise Exception(f"{value} is not found in the tree")
289        
290        # deleting a node that has no children
291        if node.left_child is None and node.right_child is None:
292            if parent_node is None:
293                self.root = None
294            elif parent_node.left_child == node:
295                parent_node.left_child = None
296            elif parent_node.right_child == node:
297                parent_node.right_child = None
298            self._size -= 1
299        # deleting a node that has only one child
300        elif node.left_child is None or node.right_child is None:
301            child_node = node.left_child if node.left_child else node.right_child
302            if parent_node is None:
303                self.root = child_node
304            elif parent_node.left_child == node:
305                parent_node.left_child = child_node
306            else:
307                parent_node.right_child = child_node
308            if child_node:
309                child_node.parent = parent_node
310        # deleting a node with two children (using in order successor)
311        else:
312            successor_parent = node
313            successor = node.right_child
314            while successor.left_child is not None:
315                successor_parent = successor
316                successor = successor.left_child
317            
318            node.value = successor.value
319            if successor_parent.left_child == successor:
320                successor_parent.left_child = successor.right_child
321            else:
322                successor_parent.right_child = successor.right_child
323            
324            if successor.right_child:  # Update parent if successor had a right child
325                successor.right_child.parent = successor_parent
326            self._size -= 1
def find_min(self, node=None, return_value=False, none_value=None):
328    def find_min(self, node=None, return_value=False, none_value=None):
329        self._check_empty()
330        if node is none_value:
331            current_node = self.root
332        else:
333            current_node = node
334            
335        while current_node.left_child is not none_value:
336            current_node = current_node.left_child
337        
338        if return_value is True:
339            return current_node.value
340        return current_node
def find_max(self, node=None, return_value=False, none_value=None):
342    def find_max(self, node=None, return_value=False, none_value=None):
343        self._check_empty()
344        if node is none_value:
345            current_node = self.root
346        else:
347            current_node = node
348            
349        while current_node.right_child is not none_value:
350            current_node = current_node.right_child
351        if return_value is True:
352            return current_node.value
353        return current_node
def height(self, opt_node=<object object>):
355    def height(self, opt_node=_sentinel):
356        if opt_node is _sentinel:
357            return self.height(self.root)
358        if opt_node is None:
359            return 0
360        else:
361            left_height = self.height(opt_node.left_child)
362            right_height = self.height(opt_node.right_child)
363            return max(left_height, right_height) + 1
def get_size(self):
365    def get_size(self):
366        if self.root is None:
367            return 0
368
369        def _size(node=None):
370            if node is None:
371                return 0
372            return _size(node.left_child) + _size(node.right_child) + 1
373
374        ### to be removed later
375        if _size(self.root) != self._size:
376            print("get_size() fonksiyonundan dönüldü. self_size'da hata olmalı")
377            return
378        ###
379        return _size(self.root)
def clear(self):
381    def clear(self):
382        self.root = None
383        self._size = 0
def copy(self, method='recursive'):
385    def copy(self, method="recursive"):
386        self._check_empty()
387        self._check_param_type(method, copy_keywords)
388        tree = BinarySearchTree()
389
390        def _copy_iterative():
391            for node in self._preorder(self.root):
392                tree.insert(node.value)
393            return tree
394
395        def _copy_recursive(node, parent):
396            if node is None:
397                return None
398            new_node = self._Node(node.value)
399            new_node.parent = parent
400            tree._size += 1
401            new_node.left_child = _copy_recursive(node.left_child, new_node)
402            new_node.right_child = _copy_recursive(node.right_child, new_node)
403            return new_node
404
405        if method == "iterative":
406            return _copy_iterative()
407        else:
408            tree.root = _copy_recursive(self.root, self.root.parent)
409            return tree
def is_balanced(self, node=None):
411    def is_balanced(self, node=None):
412        if node is None:
413            node = self.root
414        if not isinstance(node, TreeNode):
415            raise Exception(f"Please provide a {TreeNode} object")
416        left_subtree_height = self.height(node.left_child)
417        right_subtree_height = self.height(node.right_child)
418        return False if abs(left_subtree_height - right_subtree_height) > 1 else True
def pretty_print(self, none_value=None):
420    def pretty_print(self, none_value=None):
421        """Prints the tree in a visually appealing way in the terminal."""
422        if self._root is none_value:
423            print("(empty tree)")
424            return
425        
426        lines, *_ = self._pretty_print_helper(self._root, none_value)
427        for line in lines:
428            print(line)

Prints the tree in a visually appealing way in the terminal.

class AVLTreeNode(data_structures.TreeNode):
21class AVLTreeNode(TreeNode):
22    """This is the default used node for `AVLTree`. Inherits `TreeNode`.
23    
24    Attributes:
25        balance_factor (int): Balance factor of the node (height of the left subree - height of the right subtree).
26    """
27    def __init__(self, value=None, left_child=None, right_child=None, parent=None, balance_factor=0):
28        super().__init__(value, left_child, right_child, parent)
29        self._balance_factor =  balance_factor
30    
31    @property
32    def balance_factor(self):
33        """int: Return `balance_factor` attribute of the object."""
34        return self._balance_factor
35    
36    @balance_factor.setter
37    def balance_factor(self, value):
38        """Set the value of the `balance_factor`."""
39        self._balance_factor = value

This is the default used node for AVLTree. Inherits TreeNode.

Attributes:
  • balance_factor (int): Balance factor of the node (height of the left subree - height of the right subtree).
AVLTreeNode( value=None, left_child=None, right_child=None, parent=None, balance_factor=0)
27    def __init__(self, value=None, left_child=None, right_child=None, parent=None, balance_factor=0):
28        super().__init__(value, left_child, right_child, parent)
29        self._balance_factor =  balance_factor

Initialize a TreeNode object. Used in data_structures.BinarySearchTree.

Arguments:
  • value (Any): The stored value in the node.
  • left_child (Node | None): Left child of the current node.
  • right_child (Node | None): Right child of the current node.
  • parent (Node | None): Parent node of the current node.
balance_factor
31    @property
32    def balance_factor(self):
33        """int: Return `balance_factor` attribute of the object."""
34        return self._balance_factor

int: Return balance_factor attribute of the object.

class AVLTree(data_structures.BinarySearchTree):
 41class AVLTree(BinarySearchTree):
 42    """AVL Trees are self-balancing binary search trees. Thus, `AVLTree` object inherits `BinarySearchTree` class."""
 43    
 44    def __init__(self, value=None, node=AVLTreeNode):
 45        """Initialize an AVL Tree.
 46        
 47        Args:
 48            value (any): Value of the root node (if exists).
 49            node (any): Default node to construct the tree. Defaults to `AVLTreeNode`.
 50        """
 51        self._Node = node
 52        if value is None:
 53            self.root = None
 54            self._size = 0
 55        else:
 56            self._root = self._Node(value)
 57            self._size = 1
 58    
 59    def calculate_balance_factor(self, node=_sentinel):
 60        if node is _sentinel:
 61            return self.height(self.root.left_child) - self.height(self.root.right_child)
 62        else:
 63            return self.height(node.left_child) - self.height(node.right_child)
 64
 65    def insert(self, data):
 66        node = self.insert_iterative(data)
 67        self._rebalance_path(node)
 68        
 69    def _rebalance_path(self, node, deletion=False):
 70        while node is not None:
 71            node.balance_factor = self.calculate_balance_factor(node)
 72            if abs(node.balance_factor) > 1:
 73                self._rebalance(node, deletion)
 74            node = node.parent
 75    
 76    def _rebalance(self, node, deletion=False):
 77        case = self._get_rotation_case(node, deletion) 
 78        match case:
 79            case "LL":
 80                logging.info(f"Case {case} at node {node.value}, pivot={self._get_pivot(node, case).value}")
 81                self._rotate_right(node)
 82            case "LR":
 83                logging.info(f"Case {case} at node {node.value}, pivot={self._get_pivot(node, case).value}")
 84                self._rotate_left_right(node)
 85            case "RR":
 86                logging.info(f"Case {case} at node {node.value}, pivot={self._get_pivot(node, case).value}")
 87                self._rotate_left(node)
 88            case "RL":
 89                logging.info(f"Case {case} at node {node.value}, pivot={self._get_pivot(node, case).value}")
 90                self._rotate_right_left(node)
 91            case "R0":
 92                logging.info(f"Case {case} at node {node.value}, "
 93                             f"node.bf = {node.balance_factor}, "
 94                             f"child.bf = {node.right_child.balance_factor}"
 95                             )
 96                self._rotate_right(node)
 97            case "R-1":
 98                logging.info(f"Case {case} at node {node.value}, "
 99                             f"node.bf = {node.balance_factor}, "
100                             f"child.bf = {node.right_child.balance_factor}"
101                             )
102                self._rotate_left_right(node)
103            case "R1":
104                logging.info(f"Case {case} at node {node.value}, "
105                             f"node.bf = {node.balance_factor}, "
106                             f"child.bf = {node.right_child.balance_factor}"
107                             )
108                self._rotate_right(node)
109            case "L0":
110                logging.info(f"Case {case} at node {node.value}, "
111                             f"node.bf = {node.balance_factor}, "
112                             f"child.bf = {node.right_child.balance_factor}"
113                             )
114                self._rotate_left(node)
115            case "L-1":
116                logging.info(f"Case {case} at node {node.value}, "
117                             f"node.bf = {node.balance_factor}, "
118                             f"child.bf = {node.right_child.balance_factor}"
119                             )
120                self._rotate_right_left(node)
121            case "L1":
122                logging.info(f"Case {case} at node {node.value}, "
123                             f"node.bf = {node.balance_factor}, "
124                             f"child.bf = {node.right_child.balance_factor}"
125                             )
126                self._rotate_left(node)
127    
128    def _rotate_right(self, node): # LL
129        target_node = node.left_child
130        if node.parent is not None:
131            if node.parent.left_child == node:
132                node.parent.left_child = target_node
133            elif node.parent.right_child == node:
134                node.parent.right_child = target_node
135        else:
136            self.root = target_node
137            self.root.parent = None
138        
139        node.left_child = target_node.right_child
140        if target_node.right_child is not None:
141            target_node.right_child.parent = node
142            
143        target_node.right_child = node
144        target_node.parent = node.parent
145        node.parent = target_node
146        
147        # fix balance factor
148        target_node.balance_factor = self.calculate_balance_factor(target_node)
149        node.balance_factor = self.calculate_balance_factor(node)
150        
151    def _rotate_left(self, node): # RR
152        target_node = node.right_child
153        if node.parent is not None:
154            if node.parent.left_child == node:
155                node.parent.left_child = target_node
156            elif node.parent.right_child == node:
157                node.parent.right_child = target_node
158        else:
159            self.root = target_node
160            self.root.parent = None
161        
162        node.right_child = target_node.left_child
163        if target_node.left_child is not None:
164            target_node.left_child.parent = node
165            
166        target_node.left_child = node
167        target_node.parent = node.parent
168        node.parent = target_node
169        
170        # fix balance factor
171        target_node.balance_factor = self.calculate_balance_factor(target_node)
172        node.balance_factor = self.calculate_balance_factor(node)
173
174    def _rotate_left_right(self, node): # LR
175        self._rotate_left(node.left_child)
176        self._rotate_right(node)
177        
178        node.balance_factor = self.calculate_balance_factor(node)
179        if node.left_child:
180            node.left_child.balance_factor = self.calculate_balance_factor(node.left_child)
181        pivot = node.parent if node.parent is not None else self.root
182        pivot.balance_factor = self.calculate_balance_factor(pivot)
183    
184    def _rotate_right_left(self, node): # RL
185        self._rotate_right(node.right_child)
186        self._rotate_left(node)
187
188        node.balance_factor = self.calculate_balance_factor(node)
189        if node.right_child:
190            node.right_child.balance_factor = self.calculate_balance_factor(node.right_child)
191        pivot = node.parent if node.parent is not None else self.root
192        pivot.balance_factor = self.calculate_balance_factor(pivot)
193    
194    def _get_pivot(self, node, case):
195        match case:
196            case "LL":
197                return node.left_child
198            case "LR":
199                return node.left_child.right_child
200            case "RR":
201                return node.right_child
202            case "RL":
203                return node.right_child.left_child
204    
205    def _get_rotation_case(self, node, deletion=False):
206        if deletion is True:
207            if node.balance_factor > 1:
208                match node.left_child.balance_factor:
209                    case 0:
210                        return "R0"
211                    case -1:
212                        return "R-1"
213                    case 1:
214                        return "R1"
215            elif node.balance_factor < -1:
216                match node.right_child.balance_factor:
217                    case 0:
218                        return "L0"
219                    case -1:
220                        return "L1"
221                    case 1:
222                        return "L-1"
223        else: # insertion
224            if node.balance_factor > 1:
225                if node.left_child.balance_factor >= 0:
226                    return "LL"
227                return "LR" # node.left_child.balance_factor < 0
228            elif node.balance_factor < -1:
229                if node.right_child.balance_factor <= 0:
230                    return "RR"
231                return "RL" # node.right_child.balance_factor > 0
232    
233    def delete(self, value):
234        node = self.search_element(value)
235        parent_node = node.parent
236        self.delete_node(node.value, node)
237        self._rebalance_path(parent_node, deletion=True)

AVL Trees are self-balancing binary search trees. Thus, AVLTree object inherits BinarySearchTree class.

AVLTree(value=None, node=<class 'AVLTreeNode'>)
44    def __init__(self, value=None, node=AVLTreeNode):
45        """Initialize an AVL Tree.
46        
47        Args:
48            value (any): Value of the root node (if exists).
49            node (any): Default node to construct the tree. Defaults to `AVLTreeNode`.
50        """
51        self._Node = node
52        if value is None:
53            self.root = None
54            self._size = 0
55        else:
56            self._root = self._Node(value)
57            self._size = 1

Initialize an AVL Tree.

Arguments:
  • value (any): Value of the root node (if exists).
  • node (any): Default node to construct the tree. Defaults to AVLTreeNode.
def calculate_balance_factor(self, node=<object object>):
59    def calculate_balance_factor(self, node=_sentinel):
60        if node is _sentinel:
61            return self.height(self.root.left_child) - self.height(self.root.right_child)
62        else:
63            return self.height(node.left_child) - self.height(node.right_child)
def insert(self, data):
65    def insert(self, data):
66        node = self.insert_iterative(data)
67        self._rebalance_path(node)
def delete(self, value):
233    def delete(self, value):
234        node = self.search_element(value)
235        parent_node = node.parent
236        self.delete_node(node.value, node)
237        self._rebalance_path(parent_node, deletion=True)
class Color(enum.Enum):
14class Color(Enum):
15    RED     = "red"
16    BLACK   = "black"
RED = <Color.RED: 'red'>
BLACK = <Color.BLACK: 'black'>
class RBTreeNode(data_structures.TreeNode):
19class RBTreeNode(TreeNode):
20
21    def __init__(self, value, left_child=None, right_child=None, color=Color.RED, parent=None, nil_node=None):
22        super().__init__(value, left_child, right_child, parent)
23        self._color = color
24        self.NIL = nil_node
25        
26    @property
27    def color(self):
28        return self._color
29    
30    @color.setter
31    def color(self, color):
32        if not isinstance(color, Color):
33            raise Exception("Wrong type of color. Set either Color.RED or Color.BLACK")
34        self._color = color
35    
36    @property
37    def is_red(self):
38        return self._color == Color.RED
39
40    @property
41    def is_black(self):
42        return self._color == Color.BLACK
43    
44    @property
45    def grandparent(self):
46        if self.parent is self.NIL or self.parent.parent is self.NIL:
47            return self.NIL
48        return self.parent.parent
49    
50    @property
51    def uncle(self):
52        grand_parent = self.grandparent
53        if grand_parent is self.NIL:
54            return self.NIL
55        elif grand_parent.left_child == self.parent:
56            return grand_parent.right_child
57        else:
58            return grand_parent.left_child
59    
60    def is_left_child(self):
61        return self.parent != self.NIL and self.parent.left_child == self
62    
63    def is_right_child(self):
64        return self.parent != self.NIL and self.parent.right_child == self

This is the base node used when implementing binary search trees.

Arguments:
  • value (Any): The stored value in the node.
  • left_child (Node | None): Left child of the current node.
  • right_child (Node | None): Right child of the current node.
  • parent (Node | None): Parent node of the current node.
RBTreeNode( value, left_child=None, right_child=None, color=<Color.RED: 'red'>, parent=None, nil_node=None)
21    def __init__(self, value, left_child=None, right_child=None, color=Color.RED, parent=None, nil_node=None):
22        super().__init__(value, left_child, right_child, parent)
23        self._color = color
24        self.NIL = nil_node

Initialize a TreeNode object. Used in data_structures.BinarySearchTree.

Arguments:
  • value (Any): The stored value in the node.
  • left_child (Node | None): Left child of the current node.
  • right_child (Node | None): Right child of the current node.
  • parent (Node | None): Parent node of the current node.
NIL
color
26    @property
27    def color(self):
28        return self._color
is_red
36    @property
37    def is_red(self):
38        return self._color == Color.RED
is_black
40    @property
41    def is_black(self):
42        return self._color == Color.BLACK
grandparent
44    @property
45    def grandparent(self):
46        if self.parent is self.NIL or self.parent.parent is self.NIL:
47            return self.NIL
48        return self.parent.parent
uncle
50    @property
51    def uncle(self):
52        grand_parent = self.grandparent
53        if grand_parent is self.NIL:
54            return self.NIL
55        elif grand_parent.left_child == self.parent:
56            return grand_parent.right_child
57        else:
58            return grand_parent.left_child
def is_left_child(self):
60    def is_left_child(self):
61        return self.parent != self.NIL and self.parent.left_child == self
def is_right_child(self):
63    def is_right_child(self):
64        return self.parent != self.NIL and self.parent.right_child == self
class RedBlackTree(data_structures.BinarySearchTree):
 66class RedBlackTree(BinarySearchTree):
 67    def __init__(self, value=None, node=RBTreeNode):
 68        super().__init__(value, node)
 69        self._Node = node
 70        self.NIL = self._Node(None, color=Color.BLACK)
 71        self.NIL._left_child = self.NIL
 72        self.NIL._right_child = self.NIL
 73        self.NIL.parent = self.NIL
 74        
 75        if value is None:
 76            self._root = self.NIL
 77            self._size = 0
 78        else:
 79            self._root = self._Node(value, color=Color.BLACK, parent=self.NIL,
 80                                    left_child=self.NIL, right_child=self.NIL)
 81            self._size = 1
 82    
 83    def __iter__(self):
 84        for node in self._inorder(self.root):
 85            yield node
 86    
 87    def _inorder(self, node):
 88        if node is self.NIL:
 89            return
 90        yield from self._inorder(node.left_child)
 91        yield node
 92        yield from self._inorder(node.right_child)
 93    
 94    def _preorder(self, node):
 95        if node is self.NIL:
 96            return
 97        yield node
 98        yield from self._preorder(node.left_child)
 99        yield from self._preorder(node.right_child)
100        
101    def _postorder(self, node):
102        if node is self.NIL:
103            return
104        yield from self._postorder(node.left_child)
105        yield from self._postorder(node.right_child)
106        yield node
107    
108    def insert(self, data):
109        node = self._insert_iterative(data)
110        if self.root is node:
111            node.color = Color.BLACK
112        self._fix_insertion(node)
113    
114    def _insert_iterative(self, data):
115        if not isinstance(data, self._Node):
116            new_node = self._Node(data, left_child=self.NIL, right_child=self.NIL, parent=self.NIL, nil_node=self.NIL)
117        else:
118            new_node = data
119        
120        if self.root is self.NIL:
121            self.root = new_node
122            self._size += 1
123            return new_node
124        else:
125            current_node = self.root
126        while True:
127            if new_node.value >= current_node.value:
128                if current_node.right_child is self.NIL:
129                    current_node.right_child = new_node
130                    new_node.parent = current_node
131                    self._size += 1
132                    return new_node
133                current_node = current_node.right_child
134            elif new_node.value < current_node.value:
135                if current_node.left_child is self.NIL:
136                    current_node.left_child = new_node
137                    new_node.parent = current_node
138                    self._size += 1
139                    return new_node
140                current_node = current_node.left_child
141
142    def _fix_insertion(self, node):
143        while node.parent.is_red:
144            if node.parent.is_left_child():
145                if node.uncle.color == Color.RED:
146                    node.parent.color = Color.BLACK
147                    node.uncle.color = Color.BLACK
148                    node.grandparent.color = Color.RED
149                    node = node.grandparent
150                else: # if node.uncle.color == Color.BLACK or node.uncle is None
151                    if node.is_right_child():
152                        node = node.parent
153                        self._rotate_left(node)
154                    node.parent.color = Color.BLACK
155                    node.grandparent.color = Color.RED
156                    self._rotate_right(node.grandparent)
157            else: # node.parent.is_right_child()
158                if node.uncle.color == Color.RED:
159                    node.parent.color = Color.BLACK
160                    node.uncle.color = Color.BLACK
161                    node.grandparent.color = Color.RED
162                    node = node.grandparent
163                else: # if node.uncle.color == Color.BLACK or node.uncle is None
164                    if node.is_left_child():
165                        node = node.parent
166                        self._rotate_right(node)
167                    node.parent.color = Color.BLACK
168                    node.grandparent.color = Color.RED
169                    self._rotate_left(node.grandparent)
170        
171        self.root.color = Color.BLACK
172                
173    def _rotate_right(self, node):
174        target_node = node.left_child
175        if node.parent is not self.NIL:
176            if node.parent.left_child == node:
177                node.parent.left_child = target_node
178            elif node.parent.right_child == node:
179                node.parent.right_child = target_node
180        else:
181            self.root = target_node
182            self.root.parent = self.NIL
183        
184        node.left_child = target_node.right_child
185        if target_node.right_child is not self.NIL:
186            target_node.right_child.parent = node
187        
188        target_node.right_child = node
189        temp = node.parent
190        target_node.parent = temp
191        node.parent = target_node
192    
193    def _rotate_left(self, node):
194        target_node = node.right_child
195        if node.parent is not self.NIL:
196            if node.parent.left_child == node:
197                node.parent.left_child = target_node
198            elif node.parent.right_child == node:
199                node.parent.right_child = target_node
200        else:
201            self.root = target_node
202            self.root.parent = self.NIL
203        
204        node.right_child = target_node.left_child
205        if target_node.left_child is not self.NIL:
206            target_node.left_child.parent = node
207            
208        target_node.left_child = node
209        temp = node.parent
210        target_node.parent = temp
211        node.parent = target_node
212    
213    def delete(self, node):
214        deleted_node = self.search_iterative(node, self.NIL)
215        node_color = deleted_node.color
216        if deleted_node.left_child is self.NIL:
217            node = deleted_node.right_child
218            self._transplant(deleted_node, deleted_node.right_child)
219        elif deleted_node.right_child is self.NIL:
220            node = deleted_node.left_child
221            self._transplant(deleted_node, deleted_node.left_child)
222        else: # node has both children
223            successor = self.find_min(deleted_node.right_child, none_value=self.NIL)
224            node_color = successor.color
225            node = successor.right_child
226            
227            if successor.parent != deleted_node:
228                self._transplant(successor, successor.right_child)
229                successor.right_child = deleted_node.right_child
230                successor.right_child.parent = successor
231
232            self._transplant(deleted_node, successor)
233            successor.left_child = deleted_node.left_child
234            successor.left_child.parent = successor
235            successor.color = deleted_node.color
236
237        if node_color == Color.BLACK:
238            self._fix_deletion(node)
239            
240            
241    def _transplant(self, u, v):
242        """Replaces subtree rooted at u with subtree rooted at v."""
243        if u.parent == self.NIL:
244            self.root = v
245        elif u == u.parent.left_child:
246            u.parent.left_child = v
247        else:
248            u.parent.right_child = v
249        v.parent = u.parent
250
251    def _fix_deletion(self, node):
252        while node != self.root and node.color == Color.BLACK:
253            if node.is_left_child():
254                sibling_node = node.parent.right_child
255                if sibling_node.color == Color.RED:
256                    sibling_node.color = Color.BLACK
257                    sibling_node.parent.color = Color.RED
258                    self._rotate_left(sibling_node.parent)
259                    sibling_node = node.parent.right_child
260                elif sibling_node.is_black and sibling_node.left_child.is_black and sibling_node.right_child.is_black:
261                    sibling_node.color = Color.RED
262                    node = node.parent
263                elif sibling_node.is_black and sibling_node.left_child.is_red and sibling_node.right_child.is_black:
264                    sibling_node.left_child.color = Color.BLACK
265                    sibling_node.color = Color.RED
266                    self._rotate_right(sibling_node)
267                    sibling_node = node.parent.right_child
268                else: # sibling_node.is_black and sibling_node.right_child.is_red
269                    sibling_node.color = sibling_node.parent.color
270                    sibling_node.parent.color = Color.BLACK
271                    sibling_node.right_child.color = Color.BLACK
272                    self._rotate_left(sibling_node.parent)
273                    node = self.root
274            else:
275                sibling_node = node.parent.left_child
276                if sibling_node.is_red:
277                    sibling_node.color = Color.BLACK
278                    sibling_node.parent.color = Color.RED
279                    self._rotate_right(sibling_node.parent)
280                    sibling_node = node.parent.left_child
281                elif sibling_node.is_black and sibling_node.left_child.is_black and sibling_node.right_child.is_black:
282                    sibling_node.color = Color.RED
283                    node = node.parent
284                elif sibling_node.is_black and sibling_node.right_child.is_red and sibling_node.left_child.is_black:
285                    sibling_node.right_child.color = Color.BLACK
286                    sibling_node.color = Color.RED
287                    self._rotate_left(sibling_node)
288                    sibling_node = node.parent.left_child
289                else: # sibling_node.is_black and sibling_node.left_child.is_red
290                    sibling_node.color = sibling_node.parent.color
291                    sibling_node.parent.color = Color.BLACK
292                    sibling_node.left_child.color = Color.BLACK
293                    self._rotate_right(sibling_node.parent)
294                    node = self.root
295        
296        if node:
297            node.color = Color.BLACK
298        self.root.color = Color.BLACK
RedBlackTree(value=None, node=<class 'RBTreeNode'>)
67    def __init__(self, value=None, node=RBTreeNode):
68        super().__init__(value, node)
69        self._Node = node
70        self.NIL = self._Node(None, color=Color.BLACK)
71        self.NIL._left_child = self.NIL
72        self.NIL._right_child = self.NIL
73        self.NIL.parent = self.NIL
74        
75        if value is None:
76            self._root = self.NIL
77            self._size = 0
78        else:
79            self._root = self._Node(value, color=Color.BLACK, parent=self.NIL,
80                                    left_child=self.NIL, right_child=self.NIL)
81            self._size = 1
NIL
def insert(self, data):
108    def insert(self, data):
109        node = self._insert_iterative(data)
110        if self.root is node:
111            node.color = Color.BLACK
112        self._fix_insertion(node)
def delete(self, node):
213    def delete(self, node):
214        deleted_node = self.search_iterative(node, self.NIL)
215        node_color = deleted_node.color
216        if deleted_node.left_child is self.NIL:
217            node = deleted_node.right_child
218            self._transplant(deleted_node, deleted_node.right_child)
219        elif deleted_node.right_child is self.NIL:
220            node = deleted_node.left_child
221            self._transplant(deleted_node, deleted_node.left_child)
222        else: # node has both children
223            successor = self.find_min(deleted_node.right_child, none_value=self.NIL)
224            node_color = successor.color
225            node = successor.right_child
226            
227            if successor.parent != deleted_node:
228                self._transplant(successor, successor.right_child)
229                successor.right_child = deleted_node.right_child
230                successor.right_child.parent = successor
231
232            self._transplant(deleted_node, successor)
233            successor.left_child = deleted_node.left_child
234            successor.left_child.parent = successor
235            successor.color = deleted_node.color
236
237        if node_color == Color.BLACK:
238            self._fix_deletion(node)