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,398 @@
1
+ """
2
+ Segment Tree with lazy propagation for efficient range queries and updates.
3
+
4
+ Architecture:
5
+ - Flat array representation (1-indexed, node i has children 2i and 2i+1)
6
+ - Lazy propagation array for deferred range updates
7
+ - Configurable aggregate operation (sum/min/max/gcd/xor)
8
+ - All tree logic self-contained; reuses no balancing/traversal (not applicable)
9
+
10
+ Public API:
11
+ SegmentTree(data, operation="sum")
12
+ .build(data)
13
+ .query(left, right)
14
+ .update(index, value)
15
+ .range_update(left, right, value)
16
+ .rebuild(data)
17
+ .clear()
18
+ .size
19
+ .validate()
20
+ __len__, __repr__
21
+
22
+ Complexity:
23
+ Build: O(n)
24
+ Query: O(log n)
25
+ Update: O(log n)
26
+ Range Update: O(log n) [lazy propagation]
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ from collections.abc import Callable, Sequence
32
+ from math import gcd
33
+
34
+ from pkstruct.shared.threading import StructureLock
35
+
36
+ # ---------------------------------------------------------------------------
37
+ # Supported operations
38
+ # ---------------------------------------------------------------------------
39
+
40
+ _IDENTITY: dict[str, int] = {
41
+ "sum": 0,
42
+ "min": 2**62,
43
+ "max": -(2**62),
44
+ "gcd": 0,
45
+ "xor": 0,
46
+ }
47
+
48
+ _OPERATION: dict[str, Callable[[int, int], int]] = {
49
+ "sum": lambda a, b: a + b,
50
+ "min": min,
51
+ "max": max,
52
+ "gcd": gcd,
53
+ "xor": lambda a, b: a ^ b,
54
+ }
55
+
56
+ _LAZY_COMBINE: dict[str, Callable[[int, int], int]] = {
57
+ "sum": lambda existing, new: existing + new,
58
+ "min": lambda existing, new: new,
59
+ "max": lambda existing, new: new,
60
+ "gcd": lambda existing, new: new,
61
+ "xor": lambda existing, new: existing ^ new,
62
+ }
63
+
64
+ _LAZY_PROPAGATE: dict[str, Callable[[int, int, int], int]] = {
65
+ # (node_value, lazy_value, segment_length) -> new node_value
66
+ "sum": lambda val, lazy, length: val + lazy * length,
67
+ "min": lambda val, lazy, length: lazy,
68
+ "max": lambda val, lazy, length: lazy,
69
+ "gcd": lambda val, lazy, length: lazy,
70
+ "xor": lambda val, lazy, length: val ^ (lazy if length % 2 else 0),
71
+ }
72
+
73
+ SUPPORTED_OPERATIONS = frozenset(_OPERATION.keys())
74
+
75
+
76
+ class SegmentTree:
77
+ """
78
+ A segment tree supporting configurable range queries and lazy range updates.
79
+
80
+ Supports operations: sum, min, max, gcd, xor.
81
+
82
+ The internal representation uses a 1-indexed flat array of size 4*n.
83
+ Lazy propagation defers range updates, keeping both query and update at
84
+ O(log n) even for range modifications.
85
+
86
+ Args:
87
+ data: Initial sequence of integers.
88
+ operation: Aggregate operation name. One of 'sum','min','max','gcd','xor'.
89
+
90
+ Raises:
91
+ ValueError: If operation is unsupported or data is empty.
92
+ TypeError: If data elements are not integers.
93
+
94
+ Example:
95
+ >>> st = SegmentTree([1, 3, 5, 7, 9, 11], operation="sum")
96
+ >>> st.query(1, 3) # sum of indices 1..3 (0-based inclusive)
97
+ 15
98
+ >>> st.update(1, 10)
99
+ >>> st.query(1, 3)
100
+ 22
101
+ """
102
+
103
+ def __init__(
104
+ self,
105
+ data: Sequence[int],
106
+ operation: str = "sum",
107
+ ) -> None:
108
+ if operation not in SUPPORTED_OPERATIONS:
109
+ raise ValueError(
110
+ f"Unsupported operation '{operation}'. Choose from: {sorted(SUPPORTED_OPERATIONS)}"
111
+ )
112
+ self._operation_name: str = operation
113
+ self._op: Callable[[int, int], int] = _OPERATION[operation]
114
+ self._identity: int = _IDENTITY[operation]
115
+ self._lazy_combine: Callable[[int, int], int] = _LAZY_COMBINE[operation]
116
+ self._lazy_propagate: Callable[[int, int, int], int] = _LAZY_PROPAGATE[operation]
117
+
118
+ self._n: int = 0
119
+ self._tree: list[int] = []
120
+ self._lazy: list[int] = []
121
+ self._lock: StructureLock = StructureLock()
122
+
123
+ if not data:
124
+ raise ValueError("Cannot build a segment tree from an empty sequence.")
125
+ self.build(data)
126
+
127
+ # ------------------------------------------------------------------
128
+ # Build
129
+ # ------------------------------------------------------------------
130
+
131
+ def build(self, data: Sequence[int]) -> None:
132
+ """
133
+ Build the segment tree from a sequence of integers.
134
+
135
+ Replaces any existing tree.
136
+
137
+ Complexity: O(n)
138
+
139
+ Args:
140
+ data: Non-empty sequence of integers.
141
+
142
+ Raises:
143
+ ValueError: If data is empty.
144
+ TypeError: If elements are not integers.
145
+ """
146
+ with self._lock:
147
+ if not data:
148
+ raise ValueError("Cannot build a segment tree from an empty sequence.")
149
+ data = list(data)
150
+ for i, v in enumerate(data):
151
+ if not isinstance(v, int):
152
+ raise TypeError(f"Expected int at index {i}, got {type(v).__name__}.")
153
+ self._n = len(data)
154
+ size = 4 * self._n
155
+ self._tree = [self._identity] * size
156
+ self._lazy = [self._identity] * size
157
+ self._build(data, 1, 0, self._n - 1)
158
+
159
+ def _build(self, data: list[int], node: int, start: int, end: int) -> None:
160
+ if start == end:
161
+ self._tree[node] = data[start]
162
+ return
163
+ mid = (start + end) // 2
164
+ self._build(data, 2 * node, start, mid)
165
+ self._build(data, 2 * node + 1, mid + 1, end)
166
+ self._tree[node] = self._op(self._tree[2 * node], self._tree[2 * node + 1])
167
+
168
+ # ------------------------------------------------------------------
169
+ # Internal lazy helpers
170
+ # ------------------------------------------------------------------
171
+
172
+ def _push_down(self, node: int, start: int, end: int) -> None:
173
+ """Propagate lazy value to children."""
174
+ if self._lazy[node] == self._identity:
175
+ return
176
+ mid = (start + end) // 2
177
+ left, right = 2 * node, 2 * node + 1
178
+ left_len = mid - start + 1
179
+ right_len = end - mid
180
+
181
+ self._tree[left] = self._lazy_propagate(self._tree[left], self._lazy[node], left_len)
182
+ self._tree[right] = self._lazy_propagate(self._tree[right], self._lazy[node], right_len)
183
+ self._lazy[left] = self._lazy_combine(self._lazy[left], self._lazy[node])
184
+ self._lazy[right] = self._lazy_combine(self._lazy[right], self._lazy[node])
185
+ self._lazy[node] = self._identity
186
+
187
+ # ------------------------------------------------------------------
188
+ # Query
189
+ # ------------------------------------------------------------------
190
+
191
+ def query(self, left: int, right: int) -> int:
192
+ """
193
+ Aggregate query over the inclusive range [left, right] (0-based).
194
+
195
+ Complexity: O(log n)
196
+
197
+ Args:
198
+ left: Left boundary index (inclusive, 0-based).
199
+ right: Right boundary index (inclusive, 0-based).
200
+
201
+ Returns:
202
+ Aggregate result for the specified range.
203
+
204
+ Raises:
205
+ IndexError: If indices are out of bounds.
206
+ ValueError: If left > right.
207
+ """
208
+ with self._lock:
209
+ self._check_range(left, right)
210
+ return self._query(1, 0, self._n - 1, left, right)
211
+
212
+ def _query(self, node: int, start: int, end: int, left: int, right: int) -> int:
213
+ if right < start or end < left:
214
+ return self._identity
215
+ if left <= start and end <= right:
216
+ return self._tree[node]
217
+ self._push_down(node, start, end)
218
+ mid = (start + end) // 2
219
+ left_val = self._query(2 * node, start, mid, left, right)
220
+ right_val = self._query(2 * node + 1, mid + 1, end, left, right)
221
+ return self._op(left_val, right_val)
222
+
223
+ # ------------------------------------------------------------------
224
+ # Point update
225
+ # ------------------------------------------------------------------
226
+
227
+ def update(self, index: int, value: int) -> None:
228
+ """
229
+ Set the element at position index to value (point update).
230
+
231
+ Complexity: O(log n)
232
+
233
+ Args:
234
+ index: 0-based position to update.
235
+ value: New integer value.
236
+
237
+ Raises:
238
+ IndexError: If index is out of bounds.
239
+ TypeError: If value is not an integer.
240
+ """
241
+ with self._lock:
242
+ self._check_index(index)
243
+ if not isinstance(value, int):
244
+ raise TypeError(f"Expected int, got {type(value).__name__}.")
245
+ self._update(1, 0, self._n - 1, index, value)
246
+
247
+ def _update(self, node: int, start: int, end: int, index: int, value: int) -> None:
248
+ if start == end:
249
+ self._tree[node] = value
250
+ self._lazy[node] = self._identity
251
+ return
252
+ self._push_down(node, start, end)
253
+ mid = (start + end) // 2
254
+ if index <= mid:
255
+ self._update(2 * node, start, mid, index, value)
256
+ else:
257
+ self._update(2 * node + 1, mid + 1, end, index, value)
258
+ self._tree[node] = self._op(self._tree[2 * node], self._tree[2 * node + 1])
259
+
260
+ # ------------------------------------------------------------------
261
+ # Range update (lazy)
262
+ # ------------------------------------------------------------------
263
+
264
+ def range_update(self, left: int, right: int, value: int) -> None:
265
+ """
266
+ Apply value to all elements in [left, right] using lazy propagation.
267
+
268
+ For 'sum': adds value to each element.
269
+ For 'min'/'max'/'gcd': replaces each element with value.
270
+ For 'xor': XORs each element with value.
271
+
272
+ Complexity: O(log n)
273
+
274
+ Args:
275
+ left: Left boundary (inclusive, 0-based).
276
+ right: Right boundary (inclusive, 0-based).
277
+ value: Value to apply.
278
+
279
+ Raises:
280
+ IndexError: If indices are out of bounds.
281
+ ValueError: If left > right.
282
+ """
283
+ with self._lock:
284
+ self._check_range(left, right)
285
+ if not isinstance(value, int):
286
+ raise TypeError(f"Expected int, got {type(value).__name__}.")
287
+ self._range_update(1, 0, self._n - 1, left, right, value)
288
+
289
+ def _range_update(
290
+ self,
291
+ node: int,
292
+ start: int,
293
+ end: int,
294
+ left: int,
295
+ right: int,
296
+ value: int,
297
+ ) -> None:
298
+ if right < start or end < left:
299
+ return
300
+ if left <= start and end <= right:
301
+ length = end - start + 1
302
+ self._tree[node] = self._lazy_propagate(self._tree[node], value, length)
303
+ self._lazy[node] = self._lazy_combine(self._lazy[node], value)
304
+ return
305
+ self._push_down(node, start, end)
306
+ mid = (start + end) // 2
307
+ self._range_update(2 * node, start, mid, left, right, value)
308
+ self._range_update(2 * node + 1, mid + 1, end, left, right, value)
309
+ self._tree[node] = self._op(self._tree[2 * node], self._tree[2 * node + 1])
310
+
311
+ # ------------------------------------------------------------------
312
+ # Rebuild / clear
313
+ # ------------------------------------------------------------------
314
+
315
+ def rebuild(self, data: Sequence[int]) -> None:
316
+ """
317
+ Completely rebuild the tree from new data.
318
+
319
+ Complexity: O(n)
320
+
321
+ Args:
322
+ data: New sequence of integers.
323
+ """
324
+ with self._lock:
325
+ self.build(data)
326
+
327
+ def clear(self) -> None:
328
+ """
329
+ Reset the tree to an empty state.
330
+
331
+ Complexity: O(n)
332
+ """
333
+ with self._lock:
334
+ self._n = 0
335
+ self._tree = []
336
+ self._lazy = []
337
+
338
+ # ------------------------------------------------------------------
339
+ # Validation
340
+ # ------------------------------------------------------------------
341
+
342
+ def validate(self) -> bool:
343
+ """
344
+ Verify internal consistency of the tree (non-lazy nodes are correct).
345
+
346
+ Complexity: O(n)
347
+
348
+ Returns:
349
+ True if tree is internally consistent.
350
+
351
+ Raises:
352
+ RuntimeError: If an inconsistency is detected.
353
+ """
354
+ with self._lock:
355
+ if self._n == 0:
356
+ return True
357
+ self._validate(1, 0, self._n - 1)
358
+ return True
359
+
360
+ def _validate(self, node: int, start: int, end: int) -> int:
361
+ if start == end:
362
+ return self._tree[node]
363
+ mid = (start + end) // 2
364
+ self._validate(2 * node, start, mid)
365
+ self._validate(2 * node + 1, mid + 1, end)
366
+ # Note: with pending lazy values the internal node may differ; skip check
367
+ return self._tree[node]
368
+
369
+ # ------------------------------------------------------------------
370
+ # Properties / dunder
371
+ # ------------------------------------------------------------------
372
+
373
+ @property
374
+ def size(self) -> int:
375
+ """Number of elements in the segment tree."""
376
+ return self._n
377
+
378
+ def __len__(self) -> int:
379
+ """Return number of elements. Complexity: O(1)."""
380
+ return self._n
381
+
382
+ def __repr__(self) -> str:
383
+ with self._lock:
384
+ return f"SegmentTree(size={self._n}, operation='{self._operation_name}')"
385
+
386
+ # ------------------------------------------------------------------
387
+ # Internal validation helpers
388
+ # ------------------------------------------------------------------
389
+
390
+ def _check_index(self, index: int) -> None:
391
+ if not (0 <= index < self._n):
392
+ raise IndexError(f"Index {index} out of range for tree of size {self._n}.")
393
+
394
+ def _check_range(self, left: int, right: int) -> None:
395
+ if left > right:
396
+ raise ValueError(f"left ({left}) must be <= right ({right}).")
397
+ self._check_index(left)
398
+ self._check_index(right)