pkstruct 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. pkstruct/__init__.py +167 -0
  2. pkstruct/graphs/__init__.py +127 -0
  3. pkstruct/graphs/connectivity.py +157 -0
  4. pkstruct/graphs/directed.py +95 -0
  5. pkstruct/graphs/exceptions.py +63 -0
  6. pkstruct/graphs/graph.py +262 -0
  7. pkstruct/graphs/mst.py +118 -0
  8. pkstruct/graphs/scc.py +138 -0
  9. pkstruct/graphs/shortest_path.py +250 -0
  10. pkstruct/graphs/topo_sort.py +108 -0
  11. pkstruct/graphs/traversal.py +175 -0
  12. pkstruct/graphs/visualization.py +90 -0
  13. pkstruct/graphs/weighted.py +37 -0
  14. pkstruct/linear/__init__.py +95 -0
  15. pkstruct/linear/deques/__init__.py +33 -0
  16. pkstruct/linear/deques/deque.py +194 -0
  17. pkstruct/linear/deques/linked_deque.py +198 -0
  18. pkstruct/linear/exceptions.py +26 -0
  19. pkstruct/linear/linked_lists/__init__.py +5 -0
  20. pkstruct/linear/linked_lists/_base.py +608 -0
  21. pkstruct/linear/linked_lists/circular_linked_list.py +230 -0
  22. pkstruct/linear/linked_lists/doubly_linked_list.py +151 -0
  23. pkstruct/linear/linked_lists/nodes.py +68 -0
  24. pkstruct/linear/linked_lists/singly_linked_list.py +136 -0
  25. pkstruct/linear/queues/__init__.py +44 -0
  26. pkstruct/linear/queues/circular_queue.py +258 -0
  27. pkstruct/linear/queues/linked_queue.py +186 -0
  28. pkstruct/linear/queues/priority_queue.py +202 -0
  29. pkstruct/linear/queues/queue.py +174 -0
  30. pkstruct/linear/stacks/__init__.py +38 -0
  31. pkstruct/linear/stacks/array_stack.py +165 -0
  32. pkstruct/linear/stacks/linked_stack.py +168 -0
  33. pkstruct/linear/stacks/stack.py +158 -0
  34. pkstruct/linear/utils/__init__.py +18 -0
  35. pkstruct/linear/utils/benchmark.py +255 -0
  36. pkstruct/linear/utils/debug_tools.py +239 -0
  37. pkstruct/linear/utils/helpers.py +143 -0
  38. pkstruct/linear/utils/iterators.py +148 -0
  39. pkstruct/linear/visualization/__init__.py +0 -0
  40. pkstruct/linear/visualization/ascii_visualizer.py +114 -0
  41. pkstruct/linear/visualization/linked_list_visualizer.py +126 -0
  42. pkstruct/shared/__init__.py +67 -0
  43. pkstruct/shared/benchmarking/__init__.py +78 -0
  44. pkstruct/shared/debugging/__init__.py +69 -0
  45. pkstruct/shared/exceptions/__init__.py +59 -0
  46. pkstruct/shared/serializers/__init__.py +65 -0
  47. pkstruct/shared/threading/__init__.py +43 -0
  48. pkstruct/shared/validators/__init__.py +98 -0
  49. pkstruct/shared/visualization/__init__.py +21 -0
  50. pkstruct/trees/__init__.py +92 -0
  51. pkstruct/trees/avl.py +321 -0
  52. pkstruct/trees/balancing.py +253 -0
  53. pkstruct/trees/bplus.py +425 -0
  54. pkstruct/trees/bst.py +948 -0
  55. pkstruct/trees/btree.py +504 -0
  56. pkstruct/trees/exceptions.py +96 -0
  57. pkstruct/trees/fenwick_tree.py +312 -0
  58. pkstruct/trees/interval_tree.py +541 -0
  59. pkstruct/trees/node.py +356 -0
  60. pkstruct/trees/red_black.py +710 -0
  61. pkstruct/trees/segment_tree.py +398 -0
  62. pkstruct/trees/traversal.py +456 -0
  63. pkstruct/trees/tree_helpers.py +366 -0
  64. pkstruct/trees/utils/__init__.py +15 -0
  65. pkstruct/trees/utils/complexity_helpers.py +231 -0
  66. pkstruct/trees/visualization/__init__.py +0 -0
  67. pkstruct/trees/visualization/ascii_renderer.py +220 -0
  68. pkstruct/trees/visualization/tree_printer.py +129 -0
  69. pkstruct-0.1.0.dist-info/METADATA +482 -0
  70. pkstruct-0.1.0.dist-info/RECORD +72 -0
  71. pkstruct-0.1.0.dist-info/WHEEL +4 -0
  72. pkstruct-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,258 @@
1
+ """
2
+ pkstruct.linear.queues.circular_queue
3
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
+ A fixed-capacity FIFO queue backed by a ring buffer (circular array).
5
+
6
+ ``CircularQueue`` uses a pre-allocated Python ``list`` with head and tail
7
+ pointers that wrap around. Enqueue and dequeue are O(1). Once the buffer
8
+ is full, further enqueue operations raise ``QueueFullError``.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from collections.abc import Iterator
14
+ from typing import TypeVar
15
+
16
+ from pkstruct.linear.exceptions import EmptyStructureError
17
+ from pkstruct.linear.queues.queue import Queue
18
+ from pkstruct.shared.threading import StructureLock
19
+
20
+ T = TypeVar("T")
21
+
22
+
23
+ class QueueFullError(IndexError):
24
+ """Raised when attempting to enqueue into a full ``CircularQueue``."""
25
+
26
+ def __init__(self, capacity: int) -> None:
27
+ self.capacity: int = capacity
28
+ super().__init__(f"Queue is at capacity ({capacity}).")
29
+
30
+
31
+ class CircularQueue(Queue[T]):
32
+ """
33
+ A thread-safe, fixed-capacity FIFO queue using a ring buffer.
34
+
35
+ The underlying ``list`` is allocated once at creation. Head and tail
36
+ indices advance modulo capacity, avoiding the O(n) cost of shifting
37
+ elements.
38
+
39
+ Parameters
40
+ ----------
41
+ capacity:
42
+ Maximum number of elements the queue can hold.
43
+ items:
44
+ Optional iterable of initial values (front-to-rear order).
45
+ ``len(items)`` must not exceed *capacity*.
46
+
47
+ Examples
48
+ --------
49
+ >>> q = CircularQueue(3, [1, 2])
50
+ >>> q.enqueue(3)
51
+ >>> q.dequeue()
52
+ 1
53
+ >>> q.front()
54
+ 2
55
+ """
56
+
57
+ __slots__ = ("_buffer", "_capacity", "_head", "_tail", "_size", "_lock")
58
+
59
+ def __init__(self, capacity: int, items: list[T] | None = None) -> None:
60
+ if not isinstance(capacity, int) or capacity < 1:
61
+ raise ValueError(f"Capacity must be a positive integer, got {capacity!r}.")
62
+ self._capacity: int = capacity
63
+ self._buffer: list[T | None] = [None] * capacity
64
+ self._head: int = 0
65
+ self._tail: int = 0
66
+ self._size: int = 0
67
+ self._lock: StructureLock = StructureLock()
68
+ if items is not None:
69
+ if len(items) > capacity:
70
+ raise ValueError(
71
+ f"Initial items ({len(items)}) exceeds capacity ({capacity})."
72
+ )
73
+ for item in items:
74
+ self._enqueue_unsafe(item)
75
+
76
+ # ------------------------------------------------------------------ #
77
+ # Internal helpers #
78
+ # ------------------------------------------------------------------ #
79
+
80
+ def _enqueue_unsafe(self, value: T) -> None:
81
+ """Enqueue without acquiring the lock or full-check (caller must hold lock)."""
82
+ self._buffer[self._tail] = value
83
+ self._tail = (self._tail + 1) % self._capacity
84
+ self._size += 1
85
+
86
+ def _next_index(self, index: int) -> int:
87
+ """Return the next index modulo capacity."""
88
+ return (index + 1) % self._capacity
89
+
90
+ def _prev_index(self, index: int) -> int:
91
+ """Return the previous index modulo capacity."""
92
+ return (index - 1 + self._capacity) % self._capacity
93
+
94
+ # ------------------------------------------------------------------ #
95
+ # Required operations #
96
+ # ------------------------------------------------------------------ #
97
+
98
+ def enqueue(self, value: T) -> None:
99
+ """
100
+ Add *value* to the rear of the queue.
101
+
102
+ Parameters
103
+ ----------
104
+ value:
105
+ The element to enqueue.
106
+
107
+ Raises
108
+ ------
109
+ QueueFullError
110
+ If the queue is at capacity.
111
+ """
112
+ with self._lock:
113
+ if self._size == self._capacity:
114
+ raise QueueFullError(self._capacity)
115
+ self._enqueue_unsafe(value)
116
+
117
+ def dequeue(self) -> T:
118
+ """
119
+ Remove and return the front element.
120
+
121
+ Returns
122
+ -------
123
+ T
124
+
125
+ Raises
126
+ ------
127
+ EmptyStructureError
128
+ If the queue is empty.
129
+ """
130
+ with self._lock:
131
+ if self._size == 0:
132
+ raise EmptyStructureError("dequeue from an empty queue")
133
+ value = self._buffer[self._head]
134
+ self._buffer[self._head] = None
135
+ self._head = self._next_index(self._head)
136
+ self._size -= 1
137
+ return value # type: ignore[return-value]
138
+
139
+ def front(self) -> T:
140
+ """
141
+ Return the front element without removing it.
142
+
143
+ Returns
144
+ -------
145
+ T
146
+
147
+ Raises
148
+ ------
149
+ EmptyStructureError
150
+ If the queue is empty.
151
+ """
152
+ with self._lock:
153
+ if self._size == 0:
154
+ raise EmptyStructureError("front of an empty queue")
155
+ return self._buffer[self._head] # type: ignore[return-value]
156
+
157
+ def rear(self) -> T:
158
+ """
159
+ Return the rear element without removing it.
160
+
161
+ Returns
162
+ -------
163
+ T
164
+
165
+ Raises
166
+ ------
167
+ EmptyStructureError
168
+ If the queue is empty.
169
+ """
170
+ with self._lock:
171
+ if self._size == 0:
172
+ raise EmptyStructureError("rear of an empty queue")
173
+ return self._buffer[self._prev_index(self._tail)] # type: ignore[return-value]
174
+
175
+ # ------------------------------------------------------------------ #
176
+ # Query operations #
177
+ # ------------------------------------------------------------------ #
178
+
179
+ def is_empty(self) -> bool:
180
+ """Return ``True`` if the queue contains no elements."""
181
+ return self._size == 0
182
+
183
+ def is_full(self) -> bool:
184
+ """Return ``True`` if the queue is at capacity."""
185
+ return self._size == self._capacity
186
+
187
+ def size(self) -> int:
188
+ """Return the number of elements in the queue."""
189
+ return self._size
190
+
191
+ def capacity(self) -> int:
192
+ """Return the maximum number of elements the queue can hold."""
193
+ return self._capacity
194
+
195
+ def clear(self) -> None:
196
+ """Remove all elements from the queue."""
197
+ with self._lock:
198
+ self._buffer = [None] * self._capacity
199
+ self._head = 0
200
+ self._tail = 0
201
+ self._size = 0
202
+
203
+ def copy(self) -> CircularQueue[T]:
204
+ """
205
+ Return a shallow copy of this queue.
206
+
207
+ Returns
208
+ -------
209
+ CircularQueue[T]
210
+ """
211
+ with self._lock:
212
+ new = CircularQueue[T](self._capacity)
213
+ new._buffer = list(self._buffer)
214
+ new._head = self._head
215
+ new._tail = self._tail
216
+ new._size = self._size
217
+ return new
218
+
219
+ def to_list(self) -> list[T]:
220
+ """
221
+ Return a list of all elements, from front to rear.
222
+
223
+ Returns
224
+ -------
225
+ list[T]
226
+ """
227
+ with self._lock:
228
+ result: list[T] = []
229
+ idx = self._head
230
+ for _ in range(self._size):
231
+ val = self._buffer[idx]
232
+ if val is not None:
233
+ result.append(val)
234
+ idx = self._next_index(idx)
235
+ return result
236
+
237
+ # ------------------------------------------------------------------ #
238
+ # Dunder methods #
239
+ # ------------------------------------------------------------------ #
240
+
241
+ def __iter__(self) -> Iterator[T]:
242
+ with self._lock:
243
+ snapshot = self.to_list()
244
+ return iter(snapshot)
245
+
246
+ def __len__(self) -> int:
247
+ return self._size
248
+
249
+ def __bool__(self) -> bool:
250
+ return self._size > 0
251
+
252
+ def __repr__(self) -> str:
253
+ return f"CircularQueue(capacity={self._capacity}, items={self.to_list()!r})"
254
+
255
+ def __eq__(self, other: object) -> bool:
256
+ if not isinstance(other, CircularQueue):
257
+ return NotImplemented
258
+ return self.to_list() == other.to_list()
@@ -0,0 +1,186 @@
1
+ """
2
+ pkstruct.linear.queues.linked_queue
3
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
+ A FIFO queue backed by a ``SinglyLinkedList``.
5
+
6
+ ``LinkedQueue`` composes the existing production-grade linked list. Enqueue
7
+ appends at the tail; dequeue removes from the head — both O(1).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from collections.abc import Iterator
13
+ from typing import TypeVar, cast
14
+
15
+ from pkstruct.linear.exceptions import EmptyStructureError
16
+ from pkstruct.linear.linked_lists.singly_linked_list import SinglyLinkedList
17
+ from pkstruct.linear.queues.queue import Queue
18
+ from pkstruct.shared.threading import StructureLock
19
+
20
+ T = TypeVar("T")
21
+
22
+
23
+ class LinkedQueue(Queue[T]):
24
+ """
25
+ A thread-safe FIFO queue implemented on top of ``SinglyLinkedList``.
26
+
27
+ The internal list stores elements in order: front at the head, rear at
28
+ the tail. ``enqueue`` appends (tail), ``dequeue`` removes from the head
29
+ — both O(1) amortised.
30
+
31
+ Parameters
32
+ ----------
33
+ items:
34
+ Optional iterable of initial values (front-to-rear order).
35
+ If omitted the queue starts empty.
36
+
37
+ Examples
38
+ --------
39
+ >>> q = LinkedQueue([1, 2, 3])
40
+ >>> q.enqueue(4)
41
+ >>> q.dequeue()
42
+ 1
43
+ >>> q.front()
44
+ 2
45
+ """
46
+
47
+ __slots__ = ("_list", "_lock")
48
+
49
+ def __init__(self, items: list[T] | None = None) -> None:
50
+ self._list: SinglyLinkedList[T] = SinglyLinkedList()
51
+ self._lock: StructureLock = StructureLock()
52
+ if items is not None:
53
+ for item in items:
54
+ self._list.insert(item)
55
+
56
+ # ------------------------------------------------------------------ #
57
+ # Required operations #
58
+ # ------------------------------------------------------------------ #
59
+
60
+ def enqueue(self, value: T) -> None:
61
+ """
62
+ Add *value* to the rear of the queue (tail of the list).
63
+
64
+ Parameters
65
+ ----------
66
+ value:
67
+ The element to enqueue.
68
+ """
69
+ with self._lock:
70
+ self._list.insert(value)
71
+
72
+ def dequeue(self) -> T:
73
+ """
74
+ Remove and return the front element (head of the list).
75
+
76
+ Returns
77
+ -------
78
+ T
79
+
80
+ Raises
81
+ ------
82
+ EmptyStructureError
83
+ If the queue is empty.
84
+ """
85
+ with self._lock:
86
+ if self._list.is_empty():
87
+ raise EmptyStructureError("dequeue from an empty queue")
88
+ return cast(T, self._list.delete(position=0))
89
+
90
+ def front(self) -> T:
91
+ """
92
+ Return the front element without removing it.
93
+
94
+ Returns
95
+ -------
96
+ T
97
+
98
+ Raises
99
+ ------
100
+ EmptyStructureError
101
+ If the queue is empty.
102
+ """
103
+ with self._lock:
104
+ if self._list.is_empty():
105
+ raise EmptyStructureError("front of an empty queue")
106
+ return self._list.get(0)
107
+
108
+ def rear(self) -> T:
109
+ """
110
+ Return the rear element without removing it.
111
+
112
+ Returns
113
+ -------
114
+ T
115
+
116
+ Raises
117
+ ------
118
+ EmptyStructureError
119
+ If the queue is empty.
120
+ """
121
+ with self._lock:
122
+ if self._list.is_empty():
123
+ raise EmptyStructureError("rear of an empty queue")
124
+ return self._list.get(self._list.size() - 1)
125
+
126
+ # ------------------------------------------------------------------ #
127
+ # Query operations #
128
+ # ------------------------------------------------------------------ #
129
+
130
+ def is_empty(self) -> bool:
131
+ """Return ``True`` if the queue contains no elements."""
132
+ return self._list.is_empty()
133
+
134
+ def size(self) -> int:
135
+ """Return the number of elements in the queue."""
136
+ return self._list.size()
137
+
138
+ def clear(self) -> None:
139
+ """Remove all elements from the queue."""
140
+ self._list.clear()
141
+
142
+ def copy(self) -> LinkedQueue[T]:
143
+ """
144
+ Return a shallow copy of this queue.
145
+
146
+ Returns
147
+ -------
148
+ LinkedQueue[T]
149
+ """
150
+ with self._lock:
151
+ new = LinkedQueue[T]()
152
+ new._list = cast(SinglyLinkedList[T], self._list.copy())
153
+ return new
154
+
155
+ def to_list(self) -> list[T]:
156
+ """
157
+ Return a list of all elements, from front to rear.
158
+
159
+ Returns
160
+ -------
161
+ list[T]
162
+ """
163
+ return self._list.to_list()
164
+
165
+ # ------------------------------------------------------------------ #
166
+ # Dunder methods #
167
+ # ------------------------------------------------------------------ #
168
+
169
+ def __iter__(self) -> Iterator[T]:
170
+ with self._lock:
171
+ snapshot = self.to_list()
172
+ return iter(snapshot)
173
+
174
+ def __len__(self) -> int:
175
+ return self.size()
176
+
177
+ def __bool__(self) -> bool:
178
+ return not self.is_empty()
179
+
180
+ def __repr__(self) -> str:
181
+ return f"LinkedQueue({self.to_list()!r})"
182
+
183
+ def __eq__(self, other: object) -> bool:
184
+ if not isinstance(other, LinkedQueue):
185
+ return NotImplemented
186
+ return self.to_list() == other.to_list()
@@ -0,0 +1,202 @@
1
+ """
2
+ pkstruct.linear.queues.priority_queue
3
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
+ A priority queue backed by a binary heap (``heapq``).
5
+
6
+ ``PriorityQueue`` wraps the standard library ``heapq`` module, adding thread
7
+ safety and the full pkstruct queue interface. The minimum element is always
8
+ at the front. Complexity is O(log n) for ``enqueue`` and ``dequeue``.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import heapq
14
+ from collections.abc import Iterator
15
+ from typing import TypeVar
16
+
17
+ from pkstruct.linear.exceptions import EmptyStructureError
18
+ from pkstruct.linear.queues.queue import Queue
19
+ from pkstruct.shared.threading import StructureLock
20
+
21
+ T = TypeVar("T")
22
+
23
+
24
+ class PriorityQueue(Queue[T]):
25
+ """
26
+ A thread-safe min-heap priority queue.
27
+
28
+ The element with the smallest priority (or smallest value when no explicit
29
+ priority is given) is always at the front. If two elements share the same
30
+ priority, the one enqueued first is served first (stable via insertion
31
+ counter).
32
+
33
+ Parameters
34
+ ----------
35
+ items:
36
+ Optional iterable of initial values. All elements are heapified at
37
+ construction (O(n)).
38
+
39
+ Examples
40
+ --------
41
+ >>> pq = PriorityQueue([3, 1, 2])
42
+ >>> pq.dequeue()
43
+ 1
44
+ >>> pq.enqueue(0)
45
+ >>> pq.dequeue()
46
+ 0
47
+ """
48
+
49
+ __slots__ = ("_heap", "_lock", "_counter")
50
+
51
+ def __init__(self, items: list[T] | None = None) -> None:
52
+ self._heap: list[tuple[object, int, T]] = []
53
+ self._lock: StructureLock = StructureLock()
54
+ self._counter: int = 0
55
+ if items is not None:
56
+ for item in items:
57
+ self.enqueue(item)
58
+
59
+ # ------------------------------------------------------------------ #
60
+ # Required operations #
61
+ # ------------------------------------------------------------------ #
62
+
63
+ def enqueue(self, value: T, priority: object = None) -> None:
64
+ """
65
+ Add *value* to the priority queue.
66
+
67
+ Parameters
68
+ ----------
69
+ value:
70
+ The element to enqueue.
71
+ priority:
72
+ Optional sort key. If ``None``, *value* itself is used for
73
+ ordering (requires comparable elements).
74
+ """
75
+ with self._lock:
76
+ key: object = priority if priority is not None else value
77
+ heapq.heappush(self._heap, (key, self._counter, value))
78
+ self._counter += 1
79
+
80
+ def dequeue(self) -> T:
81
+ """
82
+ Remove and return the element with the smallest priority.
83
+
84
+ Returns
85
+ -------
86
+ T
87
+
88
+ Raises
89
+ ------
90
+ EmptyStructureError
91
+ If the priority queue is empty.
92
+ """
93
+ with self._lock:
94
+ if not self._heap:
95
+ raise EmptyStructureError("dequeue from an empty priority queue")
96
+ _, _, value = heapq.heappop(self._heap)
97
+ return value
98
+
99
+ def front(self) -> T:
100
+ """
101
+ Return the smallest-priority element without removing it.
102
+
103
+ Returns
104
+ -------
105
+ T
106
+
107
+ Raises
108
+ ------
109
+ EmptyStructureError
110
+ If the priority queue is empty.
111
+ """
112
+ with self._lock:
113
+ if not self._heap:
114
+ raise EmptyStructureError("front of an empty priority queue")
115
+ return self._heap[0][2]
116
+
117
+ def rear(self) -> T:
118
+ """
119
+ Return the largest-priority element without removing it.
120
+
121
+ This operation is O(n) because heap order does not directly expose
122
+ the maximum element.
123
+
124
+ Returns
125
+ -------
126
+ T
127
+
128
+ Raises
129
+ ------
130
+ EmptyStructureError
131
+ If the priority queue is empty.
132
+ """
133
+ with self._lock:
134
+ if not self._heap:
135
+ raise EmptyStructureError("rear of an empty priority queue")
136
+ return max(self._heap, key=lambda x: x[0])[2] # type: ignore[arg-type, return-value]
137
+
138
+ # ------------------------------------------------------------------ #
139
+ # Query operations #
140
+ # ------------------------------------------------------------------ #
141
+
142
+ def is_empty(self) -> bool:
143
+ """Return ``True`` if the priority queue contains no elements."""
144
+ return len(self._heap) == 0
145
+
146
+ def size(self) -> int:
147
+ """Return the number of elements in the priority queue."""
148
+ return len(self._heap)
149
+
150
+ def clear(self) -> None:
151
+ """Remove all elements from the priority queue."""
152
+ with self._lock:
153
+ self._heap.clear()
154
+ self._counter = 0
155
+
156
+ def copy(self) -> PriorityQueue[T]:
157
+ """
158
+ Return a shallow copy of this priority queue.
159
+
160
+ Returns
161
+ -------
162
+ PriorityQueue[T]
163
+ """
164
+ with self._lock:
165
+ new: PriorityQueue[T] = PriorityQueue()
166
+ new._heap = list(self._heap)
167
+ new._counter = self._counter
168
+ return new
169
+
170
+ def to_list(self) -> list[T]:
171
+ """
172
+ Return a list of all elements in priority order (smallest first).
173
+
174
+ Returns
175
+ -------
176
+ list[T]
177
+ """
178
+ with self._lock:
179
+ return [v for _, _, v in sorted(self._heap)]
180
+
181
+ # ------------------------------------------------------------------ #
182
+ # Dunder methods #
183
+ # ------------------------------------------------------------------ #
184
+
185
+ def __iter__(self) -> Iterator[T]:
186
+ with self._lock:
187
+ snapshot = self.to_list()
188
+ return iter(snapshot)
189
+
190
+ def __len__(self) -> int:
191
+ return self.size()
192
+
193
+ def __bool__(self) -> bool:
194
+ return not self.is_empty()
195
+
196
+ def __repr__(self) -> str:
197
+ return f"PriorityQueue({self.to_list()!r})"
198
+
199
+ def __eq__(self, other: object) -> bool:
200
+ if not isinstance(other, PriorityQueue):
201
+ return NotImplemented
202
+ return self.to_list() == other.to_list()