dtools.circular-array 3.9.1__py3-none-any.whl → 3.10.1__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 +7 -6
- dtools/circular_array/ca.py +250 -176
- dtools_circular_array-3.10.1.dist-info/METADATA +104 -0
- dtools_circular_array-3.10.1.dist-info/RECORD +7 -0
- {dtools_circular_array-3.9.1.dist-info → dtools_circular_array-3.10.1.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.10.1.dist-info/licenses}/LICENSE +0 -0
@@ -12,17 +12,18 @@
|
|
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
|
#### Modules
|
20
21
|
|
21
22
|
* module dtools.circular_array.ca: circular array data structure
|
22
23
|
|
23
24
|
"""
|
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
25
|
|
26
|
+
__version__ = '3.10.1'
|
27
|
+
__author__ = 'Geoffrey R. Scheller'
|
28
|
+
__copyright__ = 'Copyright (c) 2023-2025 Geoffrey R. Scheller'
|
29
|
+
__license__ = 'Apache License 2.0'
|
dtools/circular_array/ca.py
CHANGED
@@ -12,41 +12,45 @@
|
|
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
|
+
D = TypeVar('D') # Not needed for mypy, hint for pdoc.
|
23
24
|
L = TypeVar('L')
|
24
25
|
R = TypeVar('R')
|
25
26
|
U = TypeVar('U')
|
26
27
|
|
28
|
+
|
27
29
|
class ca[D](Sequence[D]):
|
28
|
-
"""
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
* raises `TypeError` if 2 or more arguments are passed to constructor
|
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
55
|
msg = f'ca expected at most 1 argument, got {len(dss)}'
|
52
56
|
raise TypeError(msg)
|
@@ -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
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])
|
@@ -121,46 +151,48 @@ class ca[D](Sequence[D]):
|
|
121
151
|
@overload
|
122
152
|
def __getitem__(self, idx: slice, /) -> ca[D]: ...
|
123
153
|
|
124
|
-
def __getitem__(self, idx: int|slice, /) -> D|ca[D]:
|
154
|
+
def __getitem__(self, idx: int | slice, /) -> D | ca[D]:
|
125
155
|
if isinstance(idx, slice):
|
126
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
184
|
_ca = ca(data)
|
156
|
-
self._data, self._cnt, self._cap, self._front, self._rear =
|
157
|
-
|
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
|
-
msg1 = 'Out of bounds: '
|
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:
|
203
|
+
if cnt < 1:
|
177
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
220
|
_ca = ca(data)
|
189
|
-
self._data, self._cnt, self._cap, self._front, self._rear =
|
190
|
-
|
191
|
-
|
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,24 +232,32 @@ 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
|
260
|
+
def pushl(self, *ds: D) -> None:
|
217
261
|
"""Push data from the left onto the ca."""
|
218
262
|
for d in ds:
|
219
263
|
if self._cnt == self._cap:
|
@@ -221,7 +265,7 @@ class ca[D](Sequence[D]):
|
|
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
|
268
|
+
def pushr(self, *ds: D) -> None:
|
225
269
|
"""Push data from the right onto the ca."""
|
226
270
|
for d in ds:
|
227
271
|
if self._cnt == self._cap:
|
@@ -229,73 +273,96 @@ class ca[D](Sequence[D]):
|
|
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
|
276
|
+
def popl(self) -> D | Never:
|
233
277
|
"""Pop one value off the left side of the ca.
|
234
278
|
|
235
|
-
|
279
|
+
Raises `ValueError` when called on an empty ca.
|
280
|
+
|
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
|
302
|
+
def popr(self) -> D | Never:
|
249
303
|
"""Pop one value off the right side of the ca.
|
250
304
|
|
251
|
-
|
305
|
+
Raises `ValueError` when called on an empty ca.
|
306
|
+
|
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
|
352
|
+
def poplt(self, max: int) -> tuple[D, ...]:
|
287
353
|
"""Pop multiple values from left side of ca.
|
288
354
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
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
|
359
|
+
|
293
360
|
"""
|
294
361
|
ds: list[D] = []
|
295
362
|
|
296
363
|
while max > 0:
|
297
364
|
try:
|
298
|
-
ds.append(self.
|
365
|
+
ds.append(self.popl())
|
299
366
|
except ValueError:
|
300
367
|
break
|
301
368
|
else:
|
@@ -303,18 +370,19 @@ class ca[D](Sequence[D]):
|
|
303
370
|
|
304
371
|
return tuple(ds)
|
305
372
|
|
306
|
-
def
|
373
|
+
def poprt(self, max: int) -> tuple[D, ...]:
|
307
374
|
"""Pop multiple values from right side of `ca`.
|
308
375
|
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
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
|
380
|
+
|
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,87 +390,88 @@ class ca[D](Sequence[D]):
|
|
322
390
|
|
323
391
|
return tuple(ds)
|
324
392
|
|
325
|
-
def
|
393
|
+
def rotl(self, n: int = 1) -> None:
|
326
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
|
401
|
+
def rotr(self, n: int = 1) -> None:
|
334
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
409
|
def map[U](self, f: Callable[[D], U], /) -> ca[U]:
|
342
410
|
"""Apply function f over contents, returns new `ca` instance.
|
343
411
|
|
344
|
-
|
345
|
-
|
412
|
+
- parameter `f` function of type `f[~D, ~U] -> ca[~U]`
|
413
|
+
- returns a new instance of type `ca[~U]`
|
414
|
+
|
346
415
|
"""
|
347
416
|
return ca(map(f, self))
|
348
417
|
|
349
|
-
def
|
418
|
+
def foldl[L](self, f: Callable[[L, D], L], /, initial: L | None = None) -> L:
|
350
419
|
"""Left fold ca via function and optional initial value.
|
351
420
|
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
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
432
|
msg = 'Method foldL called on an empty ca without an initial value.'
|
363
433
|
raise ValueError(msg)
|
364
|
-
|
365
|
-
return initial
|
366
|
-
else:
|
367
|
-
if initial is None:
|
368
|
-
acc = cast(L, self[0]) # in this case D = L
|
369
|
-
for idx in range(1, self._cnt):
|
370
|
-
acc = f(acc, self[idx])
|
371
|
-
return acc
|
372
|
-
else:
|
373
|
-
acc = initial
|
374
|
-
for d in self:
|
375
|
-
acc = f(acc, d)
|
376
|
-
return acc
|
434
|
+
return initial
|
377
435
|
|
378
|
-
|
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:
|
379
448
|
"""Right fold ca via function and optional initial value.
|
380
449
|
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
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 an empty ca without an 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
477
|
"""Returns current capacity of the ca."""
|
@@ -410,25 +479,30 @@ class ca[D](Sequence[D]):
|
|
410
479
|
|
411
480
|
def empty(self) -> None:
|
412
481
|
"""Empty the ca, keep current capacity."""
|
413
|
-
self._data, self._front, self._rear = [None]*self._cap, 0, self._cap
|
482
|
+
self._data, self._front, self._rear = [None] * self._cap, 0, self._cap
|
414
483
|
|
415
|
-
def
|
484
|
+
def fraction_filled(self) -> float:
|
416
485
|
"""Returns fractional capacity of the ca."""
|
417
|
-
return self._cnt/self._cap
|
486
|
+
return self._cnt / self._cap
|
418
487
|
|
419
|
-
def resize(self, minimum_capacity: int=2) -> None:
|
420
|
-
"""Compact `ca` and resize to `
|
488
|
+
def resize(self, minimum_capacity: int = 2) -> None:
|
489
|
+
"""Compact `ca` and resize to `minimum_capacity` if necessary.
|
490
|
+
|
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
|
|
500
|
+
|
431
501
|
def CA[D](*ds: D) -> ca[D]:
|
432
|
-
"""Function to produce a `ca` array from a variable number of arguments.
|
433
|
-
return ca(ds)
|
502
|
+
"""Function to produce a `ca` array from a variable number of arguments.
|
434
503
|
|
504
|
+
Upper case function name used to stand out in place of syntactic sugar
|
505
|
+
used by builtins, like `[]` for list or `{}` for dict or set.
|
506
|
+
|
507
|
+
"""
|
508
|
+
return ca(ds)
|
@@ -0,0 +1,104 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: dtools.circular-array
|
3
|
+
Version: 3.10.1
|
4
|
+
Summary: ### Developer Tools - circular array data structure
|
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
|
+
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
|
+
ca = CA(1, 2, 3)
|
67
|
+
assert ca.popl() == 1
|
68
|
+
assert ca.popr() == 3
|
69
|
+
ca.pushr(42, 0)
|
70
|
+
ca.pushl(0, 1)
|
71
|
+
assert repr(ca) == 'CA(1, 0, 2, 42, 0)'
|
72
|
+
assert str(ca) == '(|1, 0, 2, 42, 0|)'
|
73
|
+
|
74
|
+
ca = ca(range(1,11))
|
75
|
+
assert repr(ca) == 'CA(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)'
|
76
|
+
assert str(ca) == '(|1, 2, 3, 4, 5, 6, 7, 8, 9, 10|)'
|
77
|
+
assert len(ca) == 10
|
78
|
+
tup3 = ca.poplt(3)
|
79
|
+
tup4 = ca.poprt(4)
|
80
|
+
assert tup3 == (1, 2, 3)
|
81
|
+
assert tup4 == (10, 9, 8, 7)
|
82
|
+
|
83
|
+
assert ca == CA(4, 5, 6)
|
84
|
+
four, *rest = ca.popft(1000)
|
85
|
+
assert four == 4
|
86
|
+
assert rest == [5, 6]
|
87
|
+
assert len(ca) == 0
|
88
|
+
|
89
|
+
ca = ca([1, 2, 3])
|
90
|
+
assert ca.popld(42) == 1
|
91
|
+
assert ca.poprd(42) == 3
|
92
|
+
assert ca.popld(42) == 2
|
93
|
+
assert ca.poprd(42) == 42
|
94
|
+
assert ca.popld(42) == 42
|
95
|
+
assert len(ca) == 0
|
96
|
+
```
|
97
|
+
|
98
|
+
______________________________________________________________________
|
99
|
+
|
100
|
+
[1]: https://pypi.org/project/dtools.circular-array
|
101
|
+
[2]: https://github.com/grscheller/dtools-circular-array
|
102
|
+
[3]: https://grscheller.github.io/dtools-docs/circular-array
|
103
|
+
[4]: https://github.com/grscheller/dtools-docs
|
104
|
+
|
@@ -0,0 +1,7 @@
|
|
1
|
+
dtools/circular_array/__init__.py,sha256=-GZLNI-R4UvHgg5pLNGxdNPYO8LX4Aq6eUp9rYB3Vkg,1018
|
2
|
+
dtools/circular_array/ca.py,sha256=FZe0ksC9BFzQXqqRY8YXaEpF0g38CR6-rdMOWcReKcw,16534
|
3
|
+
dtools/circular_array/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
+
dtools_circular_array-3.10.1.dist-info/licenses/LICENSE,sha256=csqbZRvA3Nyuav1aszWvswE8CZtaKr-hMjjjcKqms7w,10774
|
5
|
+
dtools_circular_array-3.10.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
6
|
+
dtools_circular_array-3.10.1.dist-info/METADATA,sha256=49FC3FiNoCuhYf7e_gf2KNlYDYZ9Gj8m4M25g2fTCjQ,3505
|
7
|
+
dtools_circular_array-3.10.1.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.10.1.dist-info/licenses}/LICENSE
RENAMED
File without changes
|