dtools.circular-array 3.9.1__py3-none-any.whl → 3.11.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.
- dtools/circular_array/__init__.py +14 -8
- dtools/circular_array/ca.py +281 -212
- dtools_circular_array-3.11.0.dist-info/METADATA +103 -0
- dtools_circular_array-3.11.0.dist-info/RECORD +7 -0
- {dtools_circular_array-3.9.1.dist-info → dtools_circular_array-3.11.0.dist-info}/WHEEL +1 -1
- dtools_circular_array-3.9.1.dist-info/METADATA +0 -90
- dtools_circular_array-3.9.1.dist-info/RECORD +0 -7
- {dtools_circular_array-3.9.1.dist-info → dtools_circular_array-3.11.0.dist-info/licenses}/LICENSE +0 -0
@@ -12,17 +12,23 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
"""###
|
15
|
+
"""### Developer Tools - circular array data structure
|
16
16
|
|
17
|
-
Package for an indexable, sliceable circular array
|
17
|
+
Package for an indexable, sliceable, auto-resizing circular array
|
18
|
+
data structure with amortized O(1) pushes and pops either end.
|
18
19
|
|
19
|
-
####
|
20
|
+
#### Circular Array Data Structure
|
20
21
|
|
21
|
-
* module dtools.circular_array.ca
|
22
|
+
* *module* dtools.circular_array.ca
|
23
|
+
* circular array data structure
|
24
|
+
* *class* dtools.circular_array.ca.CA
|
25
|
+
* initializer takes 1 or 0 iterables
|
26
|
+
* *function* dtools.circular_array.ca.ca
|
27
|
+
* factory function taking a variable number of arguments
|
22
28
|
|
23
29
|
"""
|
24
|
-
__version__ = "3.9.1"
|
25
|
-
__author__ = "Geoffrey R. Scheller"
|
26
|
-
__copyright__ = "Copyright (c) 2023-2025 Geoffrey R. Scheller"
|
27
|
-
__license__ = "Apache License 2.0"
|
28
30
|
|
31
|
+
__version__ = '3.11.0'
|
32
|
+
__author__ = 'Geoffrey R. Scheller'
|
33
|
+
__copyright__ = 'Copyright (c) 2023-2025 Geoffrey R. Scheller'
|
34
|
+
__license__ = 'Apache License 2.0'
|
dtools/circular_array/ca.py
CHANGED
@@ -12,43 +12,47 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
"""###
|
15
|
+
"""### Module for an indexable circular array data structure."""
|
16
|
+
|
16
17
|
from __future__ import annotations
|
17
18
|
from collections.abc import Callable, Iterable, Iterator, Sequence
|
18
|
-
from typing import
|
19
|
+
from typing import cast, Never, overload, TypeVar
|
19
20
|
|
20
|
-
__all__ = [
|
21
|
+
__all__ = ['CA', 'ca']
|
21
22
|
|
22
|
-
D = TypeVar('D')
|
23
|
-
L = TypeVar('L')
|
24
|
-
R = TypeVar('R')
|
25
|
-
U = TypeVar('U')
|
23
|
+
D = TypeVar('D') # type: ignore[unused-ignore] # Needed only for pdoc
|
24
|
+
L = TypeVar('L') # type: ignore[unused-ignore] # documentation generation.
|
25
|
+
R = TypeVar('R') # type: ignore[unused-ignore] # Otherwise, not needed
|
26
|
+
U = TypeVar('U') # type: ignore[unused-ignore] # by either MyPy or Python.
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
28
|
+
|
29
|
+
class CA[D](Sequence[D]):
|
30
|
+
"""Indexable circular array data structure
|
31
|
+
|
32
|
+
- generic, stateful data structure
|
33
|
+
- lowercase class name chosen to match nomenclature for builtins
|
34
|
+
- like `list` and `tuple`
|
35
|
+
- amortized O(1) pushing and popping from either end
|
36
|
+
- O(1) random access any element
|
37
|
+
- will resize itself as needed
|
38
|
+
- sliceable
|
39
|
+
- makes defensive copies of contents for the purposes of iteration
|
40
|
+
- in boolean context returns true if not empty, false if empty
|
41
|
+
- in comparisons compare identity before equality (like builtins)
|
42
|
+
- raises `IndexError` for out-of-bounds indexing
|
43
|
+
- raises `ValueError` for popping from or folding an empty `ca`
|
43
44
|
|
44
45
|
"""
|
46
|
+
|
45
47
|
__slots__ = '_data', '_cnt', '_cap', '_front', '_rear'
|
46
48
|
|
47
49
|
def __init__(self, *dss: Iterable[D]) -> None:
|
48
50
|
if len(dss) < 2:
|
49
|
-
self._data: list[D|None] =
|
51
|
+
self._data: list[D | None] = (
|
52
|
+
[None] + cast(list[D | None], list(*dss)) + [None]
|
53
|
+
)
|
50
54
|
else:
|
51
|
-
msg = f'
|
55
|
+
msg = f'CA expected at most 1 argument, got {len(dss)}'
|
52
56
|
raise TypeError(msg)
|
53
57
|
self._cap = cap = len(self._data)
|
54
58
|
self._cnt = cap - 2
|
@@ -61,33 +65,55 @@ class ca[D](Sequence[D]):
|
|
61
65
|
|
62
66
|
def _double_storage_capacity(self) -> None:
|
63
67
|
if self._front <= self._rear:
|
64
|
-
self._data += [None]*self._cap
|
68
|
+
self._data += [None] * self._cap
|
65
69
|
self._cap *= 2
|
66
70
|
else:
|
67
|
-
self._data =
|
68
|
-
|
71
|
+
self._data = (
|
72
|
+
self._data[: self._front]
|
73
|
+
+ [None] * self._cap
|
74
|
+
+ self._data[self._front :]
|
75
|
+
)
|
76
|
+
self._front, self._cap = self._front + self._cap, 2 * self._cap
|
69
77
|
|
70
78
|
def _compact_storage_capacity(self) -> None:
|
71
|
-
"""Compact the
|
79
|
+
"""Compact the CA."""
|
72
80
|
match self._cnt:
|
73
81
|
case 0:
|
74
|
-
self._cap, self._front, self._rear, self._data =
|
75
|
-
2, 0, 1, [None, None]
|
82
|
+
self._cap, self._front, self._rear, self._data = 2, 0, 1, [None, None]
|
76
83
|
case 1:
|
77
|
-
self._cap, self._front, self._rear, self._data =
|
78
|
-
3,
|
84
|
+
self._cap, self._front, self._rear, self._data = (
|
85
|
+
3,
|
86
|
+
1,
|
87
|
+
1,
|
88
|
+
[None, self._data[self._front], None],
|
89
|
+
)
|
79
90
|
case _:
|
80
91
|
if self._front <= self._rear:
|
81
|
-
self._cap, self._front, self._rear, self._data =
|
82
|
-
self._cnt+2,
|
92
|
+
self._cap, self._front, self._rear, self._data = (
|
93
|
+
self._cnt + 2,
|
94
|
+
1,
|
95
|
+
self._cnt,
|
96
|
+
[None] + self._data[self._front : self._rear + 1] + [None],
|
97
|
+
)
|
83
98
|
else:
|
84
|
-
self._cap, self._front, self._rear, self._data =
|
85
|
-
self._cnt+2,
|
99
|
+
self._cap, self._front, self._rear, self._data = (
|
100
|
+
self._cnt + 2,
|
101
|
+
1,
|
102
|
+
self._cnt,
|
103
|
+
[None]
|
104
|
+
+ self._data[self._front :]
|
105
|
+
+ self._data[: self._rear + 1]
|
106
|
+
+ [None],
|
107
|
+
)
|
86
108
|
|
87
109
|
def __iter__(self) -> Iterator[D]:
|
88
110
|
if self._cnt > 0:
|
89
|
-
capacity, rear, position, current_state =
|
90
|
-
self._cap,
|
111
|
+
capacity, rear, position, current_state = (
|
112
|
+
self._cap,
|
113
|
+
self._rear,
|
114
|
+
self._front,
|
115
|
+
self._data.copy(),
|
116
|
+
)
|
91
117
|
|
92
118
|
while position != rear:
|
93
119
|
yield cast(D, current_state[position])
|
@@ -96,8 +122,12 @@ class ca[D](Sequence[D]):
|
|
96
122
|
|
97
123
|
def __reversed__(self) -> Iterator[D]:
|
98
124
|
if self._cnt > 0:
|
99
|
-
capacity, front, position, current_state =
|
100
|
-
self._cap,
|
125
|
+
capacity, front, position, current_state = (
|
126
|
+
self._cap,
|
127
|
+
self._front,
|
128
|
+
self._rear,
|
129
|
+
self._data.copy(),
|
130
|
+
)
|
101
131
|
|
102
132
|
while position != front:
|
103
133
|
yield cast(D, current_state[position])
|
@@ -105,7 +135,7 @@ class ca[D](Sequence[D]):
|
|
105
135
|
yield cast(D, current_state[position])
|
106
136
|
|
107
137
|
def __repr__(self) -> str:
|
108
|
-
return '
|
138
|
+
return 'ca(' + ', '.join(map(repr, self)) + ')'
|
109
139
|
|
110
140
|
def __str__(self) -> str:
|
111
141
|
return '(|' + ', '.join(map(str, self)) + '|)'
|
@@ -119,48 +149,50 @@ class ca[D](Sequence[D]):
|
|
119
149
|
@overload
|
120
150
|
def __getitem__(self, idx: int, /) -> D: ...
|
121
151
|
@overload
|
122
|
-
def __getitem__(self, idx: slice, /) ->
|
152
|
+
def __getitem__(self, idx: slice, /) -> CA[D]: ...
|
123
153
|
|
124
|
-
def __getitem__(self, idx: int|slice, /) -> D|
|
154
|
+
def __getitem__(self, idx: int | slice, /) -> D | CA[D]:
|
125
155
|
if isinstance(idx, slice):
|
126
|
-
return
|
156
|
+
return CA(list(self)[idx])
|
127
157
|
|
128
158
|
cnt = self._cnt
|
129
159
|
if 0 <= idx < cnt:
|
130
160
|
return cast(D, self._data[(self._front + idx) % self._cap])
|
131
|
-
|
161
|
+
|
162
|
+
if -cnt <= idx < 0:
|
132
163
|
return cast(D, self._data[(self._front + cnt + idx) % self._cap])
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
msg0 = 'Trying to get a value from an empty ca.'
|
143
|
-
raise IndexError(msg0)
|
164
|
+
|
165
|
+
if cnt == 0:
|
166
|
+
msg0 = 'Trying to get a value from an empty CA.'
|
167
|
+
raise IndexError(msg0)
|
168
|
+
|
169
|
+
msg1 = 'Out of bounds: '
|
170
|
+
msg2 = f'index = {idx} not between {-cnt} and {cnt - 1} '
|
171
|
+
msg3 = 'while getting value from a CA.'
|
172
|
+
raise IndexError(msg1 + msg2 + msg3)
|
144
173
|
|
145
174
|
@overload
|
146
175
|
def __setitem__(self, idx: int, vals: D, /) -> None: ...
|
147
176
|
@overload
|
148
177
|
def __setitem__(self, idx: slice, vals: Iterable[D], /) -> None: ...
|
149
178
|
|
150
|
-
def __setitem__(self, idx: int|slice, vals: D|Iterable[D], /) -> None:
|
179
|
+
def __setitem__(self, idx: int | slice, vals: D | Iterable[D], /) -> None:
|
151
180
|
if isinstance(idx, slice):
|
152
181
|
if isinstance(vals, Iterable):
|
153
182
|
data = list(self)
|
154
183
|
data[idx] = vals
|
155
|
-
_ca =
|
156
|
-
self._data, self._cnt, self._cap, self._front, self._rear =
|
157
|
-
|
184
|
+
_ca = CA(data)
|
185
|
+
self._data, self._cnt, self._cap, self._front, self._rear = (
|
186
|
+
_ca._data,
|
187
|
+
_ca._cnt,
|
188
|
+
_ca._cap,
|
189
|
+
_ca._front,
|
190
|
+
_ca._rear,
|
191
|
+
)
|
158
192
|
return
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
foo.__delitem__(2)
|
163
|
-
raise TypeError(msg)
|
193
|
+
|
194
|
+
msg = 'must assign iterable to extended slice'
|
195
|
+
raise TypeError(msg)
|
164
196
|
|
165
197
|
cnt = self._cnt
|
166
198
|
if 0 <= idx < cnt:
|
@@ -168,27 +200,31 @@ class ca[D](Sequence[D]):
|
|
168
200
|
elif -cnt <= idx < 0:
|
169
201
|
self._data[(self._front + cnt + idx) % self._cap] = cast(D, vals)
|
170
202
|
else:
|
171
|
-
if cnt
|
172
|
-
|
173
|
-
msg2 = f'index = {idx} not between {-cnt} and {cnt-1} '
|
174
|
-
msg3 = 'while setting value from a ca.'
|
175
|
-
raise IndexError(msg1 + msg2 + msg3)
|
176
|
-
else:
|
177
|
-
msg0 = 'Trying to set a value from an empty ca.'
|
203
|
+
if cnt < 1:
|
204
|
+
msg0 = 'Trying to set a value from an empty CA.'
|
178
205
|
raise IndexError(msg0)
|
179
206
|
|
207
|
+
msg1 = 'Out of bounds: '
|
208
|
+
msg2 = f'index = {idx} not between {-cnt} and {cnt - 1} '
|
209
|
+
msg3 = 'while setting value from a CA.'
|
210
|
+
raise IndexError(msg1 + msg2 + msg3)
|
211
|
+
|
180
212
|
@overload
|
181
213
|
def __delitem__(self, idx: int) -> None: ...
|
182
214
|
@overload
|
183
215
|
def __delitem__(self, idx: slice) -> None: ...
|
184
216
|
|
185
|
-
def __delitem__(self, idx: int|slice) -> None:
|
217
|
+
def __delitem__(self, idx: int | slice) -> None:
|
186
218
|
data = list(self)
|
187
219
|
del data[idx]
|
188
|
-
_ca =
|
189
|
-
self._data, self._cnt, self._cap, self._front, self._rear =
|
190
|
-
|
191
|
-
|
220
|
+
_ca = CA(data)
|
221
|
+
self._data, self._cnt, self._cap, self._front, self._rear = (
|
222
|
+
_ca._data,
|
223
|
+
_ca._cnt,
|
224
|
+
_ca._cap,
|
225
|
+
_ca._front,
|
226
|
+
_ca._rear,
|
227
|
+
)
|
192
228
|
|
193
229
|
def __eq__(self, other: object, /) -> bool:
|
194
230
|
if self is other:
|
@@ -196,125 +232,157 @@ class ca[D](Sequence[D]):
|
|
196
232
|
if not isinstance(other, type(self)):
|
197
233
|
return False
|
198
234
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
self.
|
203
|
-
|
204
|
-
self._cap,
|
235
|
+
front1, front2, count1, count2, capacity1, capacity2 = (
|
236
|
+
self._front,
|
237
|
+
other._front,
|
238
|
+
self._cnt,
|
239
|
+
other._cnt,
|
240
|
+
self._cap,
|
241
|
+
other._cap,
|
242
|
+
)
|
205
243
|
|
206
|
-
if
|
244
|
+
if count1 != count2:
|
207
245
|
return False
|
208
246
|
|
209
|
-
for nn in range(
|
210
|
-
if
|
247
|
+
for nn in range(count1):
|
248
|
+
if (
|
249
|
+
self._data[(front1 + nn) % capacity1]
|
250
|
+
is other._data[(front2 + nn) % capacity2]
|
251
|
+
):
|
211
252
|
continue
|
212
|
-
if
|
253
|
+
if (
|
254
|
+
self._data[(front1 + nn) % capacity1]
|
255
|
+
!= other._data[(front2 + nn) % capacity2]
|
256
|
+
):
|
213
257
|
return False
|
214
258
|
return True
|
215
259
|
|
216
|
-
def
|
217
|
-
"""Push data from the left onto the
|
260
|
+
def pushl(self, *ds: D) -> None:
|
261
|
+
"""Push data from the left onto the CA."""
|
218
262
|
for d in ds:
|
219
263
|
if self._cnt == self._cap:
|
220
264
|
self._double_storage_capacity()
|
221
265
|
self._front = (self._front - 1) % self._cap
|
222
266
|
self._data[self._front], self._cnt = d, self._cnt + 1
|
223
267
|
|
224
|
-
def
|
225
|
-
"""Push data from the right onto the
|
268
|
+
def pushr(self, *ds: D) -> None:
|
269
|
+
"""Push data from the right onto the CA."""
|
226
270
|
for d in ds:
|
227
271
|
if self._cnt == self._cap:
|
228
272
|
self._double_storage_capacity()
|
229
273
|
self._rear = (self._rear + 1) % self._cap
|
230
274
|
self._data[self._rear], self._cnt = d, self._cnt + 1
|
231
275
|
|
232
|
-
def
|
233
|
-
"""Pop one value off the left side of the
|
276
|
+
def popl(self) -> D | Never:
|
277
|
+
"""Pop one value off the left side of the CA.
|
278
|
+
|
279
|
+
Raises `ValueError` when called on an empty CA.
|
234
280
|
|
235
|
-
* raises `ValueError` when called on an empty ca
|
236
281
|
"""
|
237
282
|
if self._cnt > 1:
|
238
|
-
d, self._data[self._front], self._front, self._cnt =
|
239
|
-
self._data[self._front],
|
240
|
-
|
241
|
-
|
242
|
-
|
283
|
+
d, self._data[self._front], self._front, self._cnt = (
|
284
|
+
self._data[self._front],
|
285
|
+
None,
|
286
|
+
(self._front + 1) % self._cap,
|
287
|
+
self._cnt - 1,
|
288
|
+
)
|
289
|
+
elif self._cnt == 1:
|
290
|
+
d, self._data[self._front], self._cnt, self._front, self._rear = (
|
291
|
+
self._data[self._front],
|
292
|
+
None,
|
293
|
+
0,
|
294
|
+
0,
|
295
|
+
self._cap - 1,
|
296
|
+
)
|
243
297
|
else:
|
244
|
-
|
245
|
-
|
298
|
+
msg = 'Method popl called on an empty CA'
|
299
|
+
raise ValueError(msg)
|
246
300
|
return cast(D, d)
|
247
301
|
|
248
|
-
def
|
249
|
-
"""Pop one value off the right side of the
|
302
|
+
def popr(self) -> D | Never:
|
303
|
+
"""Pop one value off the right side of the CA.
|
304
|
+
|
305
|
+
Raises `ValueError` when called on an empty CA.
|
250
306
|
|
251
|
-
* raises `ValueError` when called on an empty ca
|
252
307
|
"""
|
253
|
-
if self._cnt >
|
254
|
-
d, self._data[self._rear], self._rear, self._cnt =
|
255
|
-
self._data[self._rear],
|
256
|
-
|
257
|
-
|
258
|
-
|
308
|
+
if self._cnt > 1:
|
309
|
+
d, self._data[self._rear], self._rear, self._cnt = (
|
310
|
+
self._data[self._rear],
|
311
|
+
None,
|
312
|
+
(self._rear - 1) % self._cap,
|
313
|
+
self._cnt - 1,
|
314
|
+
)
|
315
|
+
elif self._cnt == 1:
|
316
|
+
d, self._data[self._front], self._cnt, self._front, self._rear = (
|
317
|
+
self._data[self._front],
|
318
|
+
None,
|
319
|
+
0,
|
320
|
+
0,
|
321
|
+
self._cap - 1,
|
322
|
+
)
|
259
323
|
else:
|
260
|
-
|
261
|
-
|
324
|
+
msg = 'Method popr called on an empty CA'
|
325
|
+
raise ValueError(msg)
|
262
326
|
return cast(D, d)
|
263
327
|
|
264
|
-
def
|
328
|
+
def popld(self, default: D, /) -> D:
|
265
329
|
"""Pop one value from left, provide a mandatory default value.
|
266
330
|
|
267
|
-
|
268
|
-
|
331
|
+
- safe version of popl
|
332
|
+
- returns a default value in the event the `CA` is empty
|
333
|
+
|
269
334
|
"""
|
270
335
|
try:
|
271
|
-
return self.
|
336
|
+
return self.popl()
|
272
337
|
except ValueError:
|
273
338
|
return default
|
274
339
|
|
275
|
-
def
|
340
|
+
def poprd(self, default: D, /) -> D:
|
276
341
|
"""Pop one value from right, provide a mandatory default value.
|
277
342
|
|
278
|
-
|
279
|
-
|
343
|
+
- safe version of popr
|
344
|
+
- returns a default value in the event the `CA` is empty
|
345
|
+
|
280
346
|
"""
|
281
347
|
try:
|
282
|
-
return self.
|
348
|
+
return self.popr()
|
283
349
|
except ValueError:
|
284
350
|
return default
|
285
351
|
|
286
|
-
def
|
287
|
-
"""Pop multiple values from left side of
|
352
|
+
def poplt(self, maximum: int) -> tuple[D, ...]:
|
353
|
+
"""Pop multiple values from left side of `CA`.
|
354
|
+
|
355
|
+
- returns the results in a tuple of type `tuple[~D, ...]`
|
356
|
+
- returns an empty tuple if `CA` is empty
|
357
|
+
- pop no more that `max` values
|
358
|
+
- will pop less if `CA` becomes empty
|
288
359
|
|
289
|
-
* returns the results in a tuple of type `tuple[~D, ...]`
|
290
|
-
* returns an empty tuple if `ca` is empty
|
291
|
-
* pop no more that `max` values
|
292
|
-
* will pop less if `ca` becomes empty
|
293
360
|
"""
|
294
361
|
ds: list[D] = []
|
295
362
|
|
296
|
-
while
|
363
|
+
while maximum > 0:
|
297
364
|
try:
|
298
|
-
ds.append(self.
|
365
|
+
ds.append(self.popl())
|
299
366
|
except ValueError:
|
300
367
|
break
|
301
368
|
else:
|
302
|
-
|
369
|
+
maximum -= 1
|
303
370
|
|
304
371
|
return tuple(ds)
|
305
372
|
|
306
|
-
def
|
307
|
-
"""Pop multiple values from right side of `
|
373
|
+
def poprt(self, max: int) -> tuple[D, ...]:
|
374
|
+
"""Pop multiple values from right side of `CA`.
|
375
|
+
|
376
|
+
- returns the results in a tuple of type `tuple[~D, ...]`
|
377
|
+
- returns an empty tuple if `CA` is empty
|
378
|
+
- pop no more that `max` values
|
379
|
+
- will pop less if `CA` becomes empty
|
308
380
|
|
309
|
-
* returns the results in a tuple of type `tuple[~D, ...]`
|
310
|
-
* returns an empty tuple if `ca` is empty
|
311
|
-
* pop no more that `max` values
|
312
|
-
* will pop less if `ca` becomes empty
|
313
381
|
"""
|
314
382
|
ds: list[D] = []
|
315
383
|
while max > 0:
|
316
384
|
try:
|
317
|
-
ds.append(self.
|
385
|
+
ds.append(self.popr())
|
318
386
|
except ValueError:
|
319
387
|
break
|
320
388
|
else:
|
@@ -322,113 +390,114 @@ class ca[D](Sequence[D]):
|
|
322
390
|
|
323
391
|
return tuple(ds)
|
324
392
|
|
325
|
-
def
|
326
|
-
"""Rotate
|
393
|
+
def rotl(self, n: int = 1) -> None:
|
394
|
+
"""Rotate `CA` arguments left n times."""
|
327
395
|
if self._cnt < 2:
|
328
396
|
return
|
329
397
|
while n > 0:
|
330
|
-
self.
|
398
|
+
self.pushr(self.popl())
|
331
399
|
n -= 1
|
332
400
|
|
333
|
-
def
|
334
|
-
"""Rotate
|
401
|
+
def rotr(self, n: int = 1) -> None:
|
402
|
+
"""Rotate `CA` arguments right n times."""
|
335
403
|
if self._cnt < 2:
|
336
404
|
return
|
337
405
|
while n > 0:
|
338
|
-
self.
|
406
|
+
self.pushl(self.popr())
|
339
407
|
n -= 1
|
340
408
|
|
341
|
-
def map[U](self, f: Callable[[D], U], /) ->
|
342
|
-
"""Apply function f over contents, returns new `
|
409
|
+
def map[U](self, f: Callable[[D], U], /) -> CA[U]:
|
410
|
+
"""Apply function f over contents, returns new `CA` instance.
|
411
|
+
|
412
|
+
- parameter `f` function of type `f[~D, ~U] -> CA[~U]`
|
413
|
+
- returns a new instance of type `CA[~U]`
|
343
414
|
|
344
|
-
* parameter `f` function of type `f[~D, ~U] -> ca[~U]`
|
345
|
-
* returns a new instance of type `ca[~U]`
|
346
415
|
"""
|
347
|
-
return
|
348
|
-
|
349
|
-
def
|
350
|
-
"""Left fold
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
416
|
+
return CA(map(f, self))
|
417
|
+
|
418
|
+
def foldl[L](self, f: Callable[[L, D], L], /, initial: L | None = None) -> L:
|
419
|
+
"""Left fold `CA` via function and optional initial value.
|
420
|
+
|
421
|
+
- parameter `f` function of type `f[~L, ~D] -> ~L`
|
422
|
+
- the first argument to `f` is for the accumulated value.
|
423
|
+
- parameter `initial` is an optional initial value
|
424
|
+
- returns the reduced value of type `~L`
|
425
|
+
- note that `~L` and `~D` can be the same type
|
426
|
+
- if an initial value is not given then by necessity `~L = ~D`
|
427
|
+
- raises `ValueError` when called on an empty `ca` and `initial` not given
|
428
|
+
|
359
429
|
"""
|
360
430
|
if self._cnt == 0:
|
361
431
|
if initial is None:
|
362
|
-
msg = 'Method
|
432
|
+
msg = 'Method foldl called on an empty `CA` without an initial value.'
|
363
433
|
raise ValueError(msg)
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
434
|
+
return initial
|
435
|
+
|
436
|
+
if initial is None:
|
437
|
+
acc = cast(L, self[0]) # in this case D = L
|
438
|
+
for idx in range(1, self._cnt):
|
439
|
+
acc = f(acc, self[idx])
|
440
|
+
return acc
|
441
|
+
|
442
|
+
acc = initial
|
443
|
+
for d in self:
|
444
|
+
acc = f(acc, d)
|
445
|
+
return acc
|
446
|
+
|
447
|
+
def foldr[R](self, f: Callable[[D, R], R], /, initial: R | None = None) -> R:
|
448
|
+
"""Right fold `CA` via function and optional initial value.
|
449
|
+
|
450
|
+
- parameter `f` function of type `f[~D, ~R] -> ~R`
|
451
|
+
- the second argument to f is for the accumulated value
|
452
|
+
- parameter `initial` is an optional initial value
|
453
|
+
- returns the reduced value of type `~R`
|
454
|
+
- note that `~R` and `~D` can be the same type
|
455
|
+
- if an initial value is not given then by necessity `~R = ~D`
|
456
|
+
- raises `ValueError` when called on an empty `CA` and `initial` not given
|
457
|
+
|
388
458
|
"""
|
389
459
|
if self._cnt == 0:
|
390
460
|
if initial is None:
|
391
|
-
msg = 'Method
|
461
|
+
msg = 'Method foldr called on empty `CA` without initial value.'
|
392
462
|
raise ValueError(msg)
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
return acc
|
463
|
+
return initial
|
464
|
+
|
465
|
+
if initial is None:
|
466
|
+
acc = cast(R, self[-1]) # in this case D = R
|
467
|
+
for idx in range(self._cnt - 2, -1, -1):
|
468
|
+
acc = f(self[idx], acc)
|
469
|
+
return acc
|
470
|
+
|
471
|
+
acc = initial
|
472
|
+
for d in reversed(self):
|
473
|
+
acc = f(d, acc)
|
474
|
+
return acc
|
406
475
|
|
407
476
|
def capacity(self) -> int:
|
408
|
-
"""Returns current capacity of the
|
477
|
+
"""Returns current capacity of the `CA`."""
|
409
478
|
return self._cap
|
410
479
|
|
411
480
|
def empty(self) -> None:
|
412
|
-
"""Empty the
|
413
|
-
self._data, self._front, self._rear = [None]*self._cap, 0, self._cap
|
481
|
+
"""Empty the `CA`, keep current capacity."""
|
482
|
+
self._data, self._front, self._rear = [None] * self._cap, 0, self._cap
|
483
|
+
|
484
|
+
def fraction_filled(self) -> float:
|
485
|
+
"""Returns fractional capacity of the `CA`."""
|
486
|
+
return self._cnt / self._cap
|
414
487
|
|
415
|
-
def
|
416
|
-
"""
|
417
|
-
return self._cnt/self._cap
|
488
|
+
def resize(self, minimum_capacity: int = 2) -> None:
|
489
|
+
"""Compact `CA` and resize to `minimum_capacity` if necessary.
|
418
490
|
|
419
|
-
|
420
|
-
"""Compact `ca` and resize to `min_cap` if necessary.
|
491
|
+
To just compact the `CA`, do not provide a minimum capacity.
|
421
492
|
|
422
|
-
* to just compact the `ca`, do not provide a min_cap
|
423
493
|
"""
|
424
494
|
self._compact_storage_capacity()
|
425
495
|
if (min_cap := minimum_capacity) > self._cap:
|
426
|
-
self._cap, self._data =
|
427
|
-
min_cap, self._data + [None]*(min_cap - self._cap)
|
496
|
+
self._cap, self._data = min_cap, self._data + [None] * (min_cap - self._cap)
|
428
497
|
if self._cnt == 0:
|
429
498
|
self._front, self._rear = 0, self._cap - 1
|
430
499
|
|
431
|
-
def CA[D](*ds: D) -> ca[D]:
|
432
|
-
"""Function to produce a `ca` array from a variable number of arguments."""
|
433
|
-
return ca(ds)
|
434
500
|
|
501
|
+
def ca[D](*ds: D) -> CA[D]:
|
502
|
+
"""Function to produce a `CA` array from a variable number of arguments."""
|
503
|
+
return CA(ds)
|
@@ -0,0 +1,103 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: dtools.circular-array
|
3
|
+
Version: 3.11.0
|
4
|
+
Summary: ### Developer Tools - circular array data structure
|
5
|
+
Keywords: circular array,circle array,CA,dequeue,dqueue,FIFO,LIFO,pop,push,indexable,auto-resizing,auto resizing,resizing
|
6
|
+
Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
|
7
|
+
Requires-Python: >=3.12
|
8
|
+
Description-Content-Type: text/markdown
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
10
|
+
Classifier: Framework :: Pytest
|
11
|
+
Classifier: Intended Audience :: Developers
|
12
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
13
|
+
Classifier: Operating System :: OS Independent
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
15
|
+
Classifier: Typing :: Typed
|
16
|
+
License-File: LICENSE
|
17
|
+
Requires-Dist: pytest >=8.3.5 ; extra == "test"
|
18
|
+
Project-URL: Changelog, https://github.com/grscheller/dtools-circular-array/blob/main/CHANGELOG.md
|
19
|
+
Project-URL: Documentation, https://grscheller.github.io/dtools-docs/circular-array
|
20
|
+
Project-URL: Source, https://github.com/grscheller/dtools-circular-array
|
21
|
+
Provides-Extra: test
|
22
|
+
|
23
|
+
# Developer Tools - Circular Array
|
24
|
+
|
25
|
+
Python package containing a module implementing a circular array data
|
26
|
+
structure,
|
27
|
+
|
28
|
+
- **Repositories**
|
29
|
+
- [dtools.circular-array][1] project on *PyPI*
|
30
|
+
- [Source code][2] on *GitHub*
|
31
|
+
- **Detailed documentation**
|
32
|
+
- [Detailed API documentation][3] on *GH-Pages*
|
33
|
+
|
34
|
+
This project is part of the [Developer Tools for Python][4] **dtools.**
|
35
|
+
namespace project.
|
36
|
+
|
37
|
+
## Overview
|
38
|
+
|
39
|
+
- O(1) amortized pushes and pops either end.
|
40
|
+
- O(1) indexing
|
41
|
+
- fully supports slicing
|
42
|
+
- safely mutates while iterating over copies of previous state
|
43
|
+
|
44
|
+
### Module circular_array
|
45
|
+
|
46
|
+
A full featured, auto resizing circular array. Python package containing
|
47
|
+
a module implementing a full featured, indexable, sliceable, double
|
48
|
+
sided, auto-resizing circular array data structure.
|
49
|
+
|
50
|
+
Useful either if used directly as an improved version of a Python List
|
51
|
+
or in a "has-a" relationship when implementing other data structures.
|
52
|
+
|
53
|
+
- *module* dtools.circular_array
|
54
|
+
- *class* ca: circular array data structure
|
55
|
+
- *function* CA: factory function to produce a ca from data
|
56
|
+
|
57
|
+
Above nomenclature modeled after of a builtin data type like `list`, where
|
58
|
+
`ca` takes an optional iterator as an argument and CA is all caps to represent
|
59
|
+
syntactic sugar like `[]` or `{}`.
|
60
|
+
|
61
|
+
#### Usage
|
62
|
+
|
63
|
+
```python
|
64
|
+
from dtools.circular_array.ca import CA
|
65
|
+
|
66
|
+
ca1 = ca(1, 2, 3)
|
67
|
+
assert ca1.popl() == 1
|
68
|
+
assert ca1.popr() == 3
|
69
|
+
ca1.pushr(42, 0)
|
70
|
+
ca1.pushl(0, 1)
|
71
|
+
assert repr(ca1) == 'ca(1, 0, 2, 42, 0)'
|
72
|
+
assert str(ca1) == '(|1, 0, 2, 42, 0|)'
|
73
|
+
|
74
|
+
ca2 = CA(range(1,11))
|
75
|
+
assert repr(ca2) == 'ca(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)'
|
76
|
+
assert str(ca2) == '(|1, 2, 3, 4, 5, 6, 7, 8, 9, 10|)'
|
77
|
+
assert len(ca2) == 10
|
78
|
+
tup3 = ca2.poplt(3)
|
79
|
+
tup4 = ca2.poprt(4)
|
80
|
+
assert tup3 == (1, 2, 3)
|
81
|
+
assert tup4 == (10, 9, 8, 7)
|
82
|
+
assert ca2 == CA(4, 5, 6)
|
83
|
+
four, *rest = ca.popft(1000)
|
84
|
+
assert four == 4
|
85
|
+
assert rest == [5, 6]
|
86
|
+
assert len(ca2) == 0
|
87
|
+
|
88
|
+
ca3 = CA([1, 2, 3])
|
89
|
+
assert ca3.popld(42) == 1
|
90
|
+
assert ca3.poprd(42) == 3
|
91
|
+
assert ca3.popld(42) == 2
|
92
|
+
assert ca3.poprd(42) == 42
|
93
|
+
assert ca3.popld(42) == 42
|
94
|
+
assert len(ca2) == 0
|
95
|
+
```
|
96
|
+
|
97
|
+
______________________________________________________________________
|
98
|
+
|
99
|
+
[1]: https://pypi.org/project/dtools.circular-array
|
100
|
+
[2]: https://github.com/grscheller/dtools-circular-array
|
101
|
+
[3]: https://grscheller.github.io/dtools-docs/circular-array
|
102
|
+
[4]: https://github.com/grscheller/dtools-docs
|
103
|
+
|
@@ -0,0 +1,7 @@
|
|
1
|
+
dtools/circular_array/__init__.py,sha256=JfokAp0Jh8b5oU6aYQKNG1_8FB1OFT4geAowCLQSGQ0,1222
|
2
|
+
dtools/circular_array/ca.py,sha256=nAyi-fuIriMt2R08Ibq_KCdKuMNM199hTfye7Ap9qj8,16604
|
3
|
+
dtools/circular_array/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
+
dtools_circular_array-3.11.0.dist-info/licenses/LICENSE,sha256=csqbZRvA3Nyuav1aszWvswE8CZtaKr-hMjjjcKqms7w,10774
|
5
|
+
dtools_circular_array-3.11.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
6
|
+
dtools_circular_array-3.11.0.dist-info/METADATA,sha256=lE6GWKO2BQU_EEKseM5UKSLqsz5Rc-s8YdAiQt8Gjvw,3491
|
7
|
+
dtools_circular_array-3.11.0.dist-info/RECORD,,
|
@@ -1,90 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.3
|
2
|
-
Name: dtools.circular-array
|
3
|
-
Version: 3.9.1
|
4
|
-
Summary: ### Circular Array
|
5
|
-
Keywords: circular array,circle array,CA,double ended queue,dequeue,dqueue,pop,push,popL,popR,pushL,pushR,indexable,auto-resizing,auto resizing,resizing
|
6
|
-
Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
|
7
|
-
Requires-Python: >=3.12
|
8
|
-
Description-Content-Type: text/markdown
|
9
|
-
Classifier: Development Status :: 5 - Production/Stable
|
10
|
-
Classifier: Framework :: Pytest
|
11
|
-
Classifier: Intended Audience :: Developers
|
12
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
13
|
-
Classifier: Operating System :: OS Independent
|
14
|
-
Classifier: Programming Language :: Python :: 3.13
|
15
|
-
Classifier: Typing :: Typed
|
16
|
-
Requires-Dist: pytest >=8.3.2 ; extra == "test"
|
17
|
-
Project-URL: Changelog, https://github.com/grscheller/dtools-circular-array/blob/main/CHANGELOG.md
|
18
|
-
Project-URL: Documentation, https://grscheller.github.io/dtools-docs/circular-array
|
19
|
-
Project-URL: Source, https://github.com/grscheller/dtools-circular-array
|
20
|
-
Provides-Extra: test
|
21
|
-
|
22
|
-
# Developer Tools - Circular Array Implementation
|
23
|
-
|
24
|
-
Python module implementing a full featured, indexable,
|
25
|
-
double sided, auto-resizing queue data structure.
|
26
|
-
|
27
|
-
* **Repositories**
|
28
|
-
* [dtools.circular-array][1] project on *PyPI*
|
29
|
-
* [Source code][2] on *GitHub*
|
30
|
-
* **Detailed documentation**
|
31
|
-
* [Detailed API documentation][3] on *GH-Pages*
|
32
|
-
|
33
|
-
This project is part of the
|
34
|
-
[Developer Tools for Python][4] **dtools.** namespace project.
|
35
|
-
|
36
|
-
### Overview
|
37
|
-
|
38
|
-
Useful if used directly as an improved version of a Python List or in
|
39
|
-
a "has-a" relationship when implementing other data structures.
|
40
|
-
|
41
|
-
* O(1) pushes and pops either end.
|
42
|
-
* O(1) indexing
|
43
|
-
* fully supports slicing
|
44
|
-
* iterates over copies of the data allowing ca to mutate
|
45
|
-
|
46
|
-
### Usage
|
47
|
-
|
48
|
-
```python
|
49
|
-
from dtools.circular_array.ca import CA
|
50
|
-
|
51
|
-
ca = CA(1, 2, 3)
|
52
|
-
assert ca.popL() == 1
|
53
|
-
assert ca.popR() == 3
|
54
|
-
ca.pushR(42, 0)
|
55
|
-
ca.pushL(0, 1)
|
56
|
-
assert repr(ca) == 'CA(1, 0, 2, 42, 0)'
|
57
|
-
assert str(ca) == '(|1, 0, 2, 42, 0|)'
|
58
|
-
|
59
|
-
ca = CA(*range(1,11))
|
60
|
-
assert repr(ca) == 'CA(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)'
|
61
|
-
assert str(ca) == '(|1, 2, 3, 4, 5, 6, 7, 8, 9, 10|)'
|
62
|
-
assert len(ca) == 10
|
63
|
-
tup3 = ca.popLT(3)
|
64
|
-
tup4 = ca.popRT(4)
|
65
|
-
assert tup3 == (1, 2, 3)
|
66
|
-
assert tup4 == (10, 9, 8, 7)
|
67
|
-
|
68
|
-
assert ca == CA(4, 5, 6)
|
69
|
-
four, *rest = ca.popFT(1000)
|
70
|
-
assert four == 4
|
71
|
-
assert rest == [5, 6]
|
72
|
-
assert len(ca) == 0
|
73
|
-
|
74
|
-
ca = CA(1, 2, 3)
|
75
|
-
assert ca.popLD(42) == 1
|
76
|
-
assert ca.popRD(42) == 3
|
77
|
-
assert ca.popLD(42) == 2
|
78
|
-
assert ca.popRD(42) == 42
|
79
|
-
assert ca.popLD(42) == 42
|
80
|
-
assert len(ca) == 0
|
81
|
-
```
|
82
|
-
|
83
|
-
---
|
84
|
-
|
85
|
-
[1]: https://pypi.org/project/dtools.circular-array
|
86
|
-
[2]: https://github.com/grscheller/dtools-circular-array
|
87
|
-
[3]: https://grscheller.github.io/dtools-docs/circular-array
|
88
|
-
[4]: https://github.com/grscheller/dtools-docs
|
89
|
-
|
90
|
-
|
@@ -1,7 +0,0 @@
|
|
1
|
-
dtools/circular_array/__init__.py,sha256=HdCpPnMesO8z3CGuoNfqMjYLRmiedHYdcQTGE6e6qf8,922
|
2
|
-
dtools/circular_array/ca.py,sha256=a-hcJ4hQhLexqX2fqsOogEIPjpzR8PQ5zAE49-Xi8tc,15722
|
3
|
-
dtools/circular_array/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
-
dtools_circular_array-3.9.1.dist-info/LICENSE,sha256=csqbZRvA3Nyuav1aszWvswE8CZtaKr-hMjjjcKqms7w,10774
|
5
|
-
dtools_circular_array-3.9.1.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
|
6
|
-
dtools_circular_array-3.9.1.dist-info/METADATA,sha256=r80WBcYr2g_k-1uH27NICHx3be6BuKivHiI5tGyMtBY,2717
|
7
|
-
dtools_circular_array-3.9.1.dist-info/RECORD,,
|
{dtools_circular_array-3.9.1.dist-info → dtools_circular_array-3.11.0.dist-info/licenses}/LICENSE
RENAMED
File without changes
|