dtools.circular-array 3.9.0__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 +10 -8
- dtools/circular_array/ca.py +254 -175
- 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.0.dist-info → dtools_circular_array-3.10.1.dist-info}/WHEEL +1 -1
- dtools_circular_array-3.9.0.dist-info/METADATA +0 -85
- dtools_circular_array-3.9.0.dist-info/RECORD +0 -7
- {dtools_circular_array-3.9.0.dist-info → dtools_circular_array-3.10.1.dist-info/licenses}/LICENSE +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright
|
1
|
+
# Copyright 2023-2025 Geoffrey R. Scheller
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -12,16 +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 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
|
-
* module dtools.circular_array.ca: circular array
|
22
|
+
* module dtools.circular_array.ca: circular array data structure
|
22
23
|
|
23
24
|
"""
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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,36 +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
|
20
|
+
|
21
|
+
__all__ = ['ca', 'CA']
|
22
|
+
|
23
|
+
D = TypeVar('D') # Not needed for mypy, hint for pdoc.
|
24
|
+
L = TypeVar('L')
|
25
|
+
R = TypeVar('R')
|
26
|
+
U = TypeVar('U')
|
19
27
|
|
20
|
-
__all__ = [ 'ca', 'CA' ]
|
21
28
|
|
22
29
|
class ca[D](Sequence[D]):
|
23
|
-
"""
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
* raises `TypeError` if more than 2 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`
|
38
44
|
|
39
45
|
"""
|
46
|
+
|
40
47
|
__slots__ = '_data', '_cnt', '_cap', '_front', '_rear'
|
41
48
|
|
42
49
|
def __init__(self, *dss: Iterable[D]) -> None:
|
43
50
|
if len(dss) < 2:
|
44
|
-
self._data: list[D|None] =
|
51
|
+
self._data: list[D | None] = (
|
52
|
+
[None] + cast(list[D | None], list(*dss)) + [None]
|
53
|
+
)
|
45
54
|
else:
|
46
55
|
msg = f'ca expected at most 1 argument, got {len(dss)}'
|
47
56
|
raise TypeError(msg)
|
@@ -56,33 +65,55 @@ class ca[D](Sequence[D]):
|
|
56
65
|
|
57
66
|
def _double_storage_capacity(self) -> None:
|
58
67
|
if self._front <= self._rear:
|
59
|
-
self._data += [None]*self._cap
|
68
|
+
self._data += [None] * self._cap
|
60
69
|
self._cap *= 2
|
61
70
|
else:
|
62
|
-
self._data =
|
63
|
-
|
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
|
64
77
|
|
65
78
|
def _compact_storage_capacity(self) -> None:
|
66
79
|
"""Compact the ca."""
|
67
80
|
match self._cnt:
|
68
81
|
case 0:
|
69
|
-
self._cap, self._front, self._rear, self._data =
|
70
|
-
2, 0, 1, [None, None]
|
82
|
+
self._cap, self._front, self._rear, self._data = 2, 0, 1, [None, None]
|
71
83
|
case 1:
|
72
|
-
self._cap, self._front, self._rear, self._data =
|
73
|
-
3,
|
84
|
+
self._cap, self._front, self._rear, self._data = (
|
85
|
+
3,
|
86
|
+
1,
|
87
|
+
1,
|
88
|
+
[None, self._data[self._front], None],
|
89
|
+
)
|
74
90
|
case _:
|
75
91
|
if self._front <= self._rear:
|
76
|
-
self._cap, self._front, self._rear, self._data =
|
77
|
-
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
|
+
)
|
78
98
|
else:
|
79
|
-
self._cap, self._front, self._rear, self._data =
|
80
|
-
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
|
+
)
|
81
108
|
|
82
109
|
def __iter__(self) -> Iterator[D]:
|
83
110
|
if self._cnt > 0:
|
84
|
-
capacity, rear, position, current_state =
|
85
|
-
self._cap,
|
111
|
+
capacity, rear, position, current_state = (
|
112
|
+
self._cap,
|
113
|
+
self._rear,
|
114
|
+
self._front,
|
115
|
+
self._data.copy(),
|
116
|
+
)
|
86
117
|
|
87
118
|
while position != rear:
|
88
119
|
yield cast(D, current_state[position])
|
@@ -91,8 +122,12 @@ class ca[D](Sequence[D]):
|
|
91
122
|
|
92
123
|
def __reversed__(self) -> Iterator[D]:
|
93
124
|
if self._cnt > 0:
|
94
|
-
capacity, front, position, current_state =
|
95
|
-
self._cap,
|
125
|
+
capacity, front, position, current_state = (
|
126
|
+
self._cap,
|
127
|
+
self._front,
|
128
|
+
self._rear,
|
129
|
+
self._data.copy(),
|
130
|
+
)
|
96
131
|
|
97
132
|
while position != front:
|
98
133
|
yield cast(D, current_state[position])
|
@@ -116,46 +151,48 @@ class ca[D](Sequence[D]):
|
|
116
151
|
@overload
|
117
152
|
def __getitem__(self, idx: slice, /) -> ca[D]: ...
|
118
153
|
|
119
|
-
def __getitem__(self, idx: int|slice, /) -> D|ca[D]:
|
154
|
+
def __getitem__(self, idx: int | slice, /) -> D | ca[D]:
|
120
155
|
if isinstance(idx, slice):
|
121
156
|
return ca(list(self)[idx])
|
122
157
|
|
123
158
|
cnt = self._cnt
|
124
159
|
if 0 <= idx < cnt:
|
125
160
|
return cast(D, self._data[(self._front + idx) % self._cap])
|
126
|
-
|
161
|
+
|
162
|
+
if -cnt <= idx < 0:
|
127
163
|
return cast(D, self._data[(self._front + cnt + idx) % self._cap])
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
msg0 = 'Trying to get a value from an empty ca.'
|
138
|
-
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)
|
139
173
|
|
140
174
|
@overload
|
141
175
|
def __setitem__(self, idx: int, vals: D, /) -> None: ...
|
142
176
|
@overload
|
143
177
|
def __setitem__(self, idx: slice, vals: Iterable[D], /) -> None: ...
|
144
178
|
|
145
|
-
def __setitem__(self, idx: int|slice, vals: D|Iterable[D], /) -> None:
|
179
|
+
def __setitem__(self, idx: int | slice, vals: D | Iterable[D], /) -> None:
|
146
180
|
if isinstance(idx, slice):
|
147
181
|
if isinstance(vals, Iterable):
|
148
182
|
data = list(self)
|
149
183
|
data[idx] = vals
|
150
184
|
_ca = ca(data)
|
151
|
-
self._data, self._cnt, self._cap, self._front, self._rear =
|
152
|
-
|
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
|
+
)
|
153
192
|
return
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
foo.__delitem__(2)
|
158
|
-
raise TypeError(msg)
|
193
|
+
|
194
|
+
msg = 'must assign iterable to extended slice'
|
195
|
+
raise TypeError(msg)
|
159
196
|
|
160
197
|
cnt = self._cnt
|
161
198
|
if 0 <= idx < cnt:
|
@@ -163,27 +200,31 @@ class ca[D](Sequence[D]):
|
|
163
200
|
elif -cnt <= idx < 0:
|
164
201
|
self._data[(self._front + cnt + idx) % self._cap] = cast(D, vals)
|
165
202
|
else:
|
166
|
-
if cnt
|
167
|
-
msg1 = 'Out of bounds: '
|
168
|
-
msg2 = f'index = {idx} not between {-cnt} and {cnt-1} '
|
169
|
-
msg3 = 'while setting value from a ca.'
|
170
|
-
raise IndexError(msg1 + msg2 + msg3)
|
171
|
-
else:
|
203
|
+
if cnt < 1:
|
172
204
|
msg0 = 'Trying to set a value from an empty ca.'
|
173
205
|
raise IndexError(msg0)
|
174
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
|
+
|
175
212
|
@overload
|
176
213
|
def __delitem__(self, idx: int) -> None: ...
|
177
214
|
@overload
|
178
215
|
def __delitem__(self, idx: slice) -> None: ...
|
179
216
|
|
180
|
-
def __delitem__(self, idx: int|slice) -> None:
|
217
|
+
def __delitem__(self, idx: int | slice) -> None:
|
181
218
|
data = list(self)
|
182
219
|
del data[idx]
|
183
220
|
_ca = ca(data)
|
184
|
-
self._data, self._cnt, self._cap, self._front, self._rear =
|
185
|
-
|
186
|
-
|
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
|
+
)
|
187
228
|
|
188
229
|
def __eq__(self, other: object, /) -> bool:
|
189
230
|
if self is other:
|
@@ -191,24 +232,32 @@ class ca[D](Sequence[D]):
|
|
191
232
|
if not isinstance(other, type(self)):
|
192
233
|
return False
|
193
234
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
self.
|
198
|
-
|
199
|
-
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
|
+
)
|
200
243
|
|
201
|
-
if
|
244
|
+
if count1 != count2:
|
202
245
|
return False
|
203
246
|
|
204
|
-
for nn in range(
|
205
|
-
if
|
247
|
+
for nn in range(count1):
|
248
|
+
if (
|
249
|
+
self._data[(front1 + nn) % capacity1]
|
250
|
+
is other._data[(front2 + nn) % capacity2]
|
251
|
+
):
|
206
252
|
continue
|
207
|
-
if
|
253
|
+
if (
|
254
|
+
self._data[(front1 + nn) % capacity1]
|
255
|
+
!= other._data[(front2 + nn) % capacity2]
|
256
|
+
):
|
208
257
|
return False
|
209
258
|
return True
|
210
259
|
|
211
|
-
def
|
260
|
+
def pushl(self, *ds: D) -> None:
|
212
261
|
"""Push data from the left onto the ca."""
|
213
262
|
for d in ds:
|
214
263
|
if self._cnt == self._cap:
|
@@ -216,7 +265,7 @@ class ca[D](Sequence[D]):
|
|
216
265
|
self._front = (self._front - 1) % self._cap
|
217
266
|
self._data[self._front], self._cnt = d, self._cnt + 1
|
218
267
|
|
219
|
-
def
|
268
|
+
def pushr(self, *ds: D) -> None:
|
220
269
|
"""Push data from the right onto the ca."""
|
221
270
|
for d in ds:
|
222
271
|
if self._cnt == self._cap:
|
@@ -224,73 +273,96 @@ class ca[D](Sequence[D]):
|
|
224
273
|
self._rear = (self._rear + 1) % self._cap
|
225
274
|
self._data[self._rear], self._cnt = d, self._cnt + 1
|
226
275
|
|
227
|
-
def
|
276
|
+
def popl(self) -> D | Never:
|
228
277
|
"""Pop one value off the left side of the ca.
|
229
278
|
|
230
|
-
|
279
|
+
Raises `ValueError` when called on an empty ca.
|
280
|
+
|
231
281
|
"""
|
232
282
|
if self._cnt > 1:
|
233
|
-
d, self._data[self._front], self._front, self._cnt =
|
234
|
-
self._data[self._front],
|
235
|
-
|
236
|
-
|
237
|
-
|
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
|
+
)
|
238
297
|
else:
|
239
|
-
|
240
|
-
|
298
|
+
msg = 'Method popl called on an empty ca'
|
299
|
+
raise ValueError(msg)
|
241
300
|
return cast(D, d)
|
242
301
|
|
243
|
-
def
|
302
|
+
def popr(self) -> D | Never:
|
244
303
|
"""Pop one value off the right side of the ca.
|
245
304
|
|
246
|
-
|
305
|
+
Raises `ValueError` when called on an empty ca.
|
306
|
+
|
247
307
|
"""
|
248
|
-
if self._cnt >
|
249
|
-
d, self._data[self._rear], self._rear, self._cnt =
|
250
|
-
self._data[self._rear],
|
251
|
-
|
252
|
-
|
253
|
-
|
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
|
+
)
|
254
323
|
else:
|
255
|
-
|
256
|
-
|
324
|
+
msg = 'Method popr called on an empty ca'
|
325
|
+
raise ValueError(msg)
|
257
326
|
return cast(D, d)
|
258
327
|
|
259
|
-
def
|
328
|
+
def popld(self, default: D, /) -> D:
|
260
329
|
"""Pop one value from left, provide a mandatory default value.
|
261
330
|
|
262
|
-
|
263
|
-
|
331
|
+
- safe version of popl
|
332
|
+
- returns a default value in the event the `ca` is empty
|
333
|
+
|
264
334
|
"""
|
265
335
|
try:
|
266
|
-
return self.
|
336
|
+
return self.popl()
|
267
337
|
except ValueError:
|
268
338
|
return default
|
269
339
|
|
270
|
-
def
|
340
|
+
def poprd(self, default: D, /) -> D:
|
271
341
|
"""Pop one value from right, provide a mandatory default value.
|
272
342
|
|
273
|
-
|
274
|
-
|
343
|
+
- safe version of popr
|
344
|
+
- returns a default value in the event the `ca` is empty
|
345
|
+
|
275
346
|
"""
|
276
347
|
try:
|
277
|
-
return self.
|
348
|
+
return self.popr()
|
278
349
|
except ValueError:
|
279
350
|
return default
|
280
351
|
|
281
|
-
def
|
352
|
+
def poplt(self, max: int) -> tuple[D, ...]:
|
282
353
|
"""Pop multiple values from left side of ca.
|
283
354
|
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
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
|
+
|
288
360
|
"""
|
289
361
|
ds: list[D] = []
|
290
362
|
|
291
363
|
while max > 0:
|
292
364
|
try:
|
293
|
-
ds.append(self.
|
365
|
+
ds.append(self.popl())
|
294
366
|
except ValueError:
|
295
367
|
break
|
296
368
|
else:
|
@@ -298,18 +370,19 @@ class ca[D](Sequence[D]):
|
|
298
370
|
|
299
371
|
return tuple(ds)
|
300
372
|
|
301
|
-
def
|
373
|
+
def poprt(self, max: int) -> tuple[D, ...]:
|
302
374
|
"""Pop multiple values from right side of `ca`.
|
303
375
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
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
|
+
|
308
381
|
"""
|
309
382
|
ds: list[D] = []
|
310
383
|
while max > 0:
|
311
384
|
try:
|
312
|
-
ds.append(self.
|
385
|
+
ds.append(self.popr())
|
313
386
|
except ValueError:
|
314
387
|
break
|
315
388
|
else:
|
@@ -317,87 +390,88 @@ class ca[D](Sequence[D]):
|
|
317
390
|
|
318
391
|
return tuple(ds)
|
319
392
|
|
320
|
-
def
|
393
|
+
def rotl(self, n: int = 1) -> None:
|
321
394
|
"""Rotate ca arguments left n times."""
|
322
395
|
if self._cnt < 2:
|
323
396
|
return
|
324
397
|
while n > 0:
|
325
|
-
self.
|
398
|
+
self.pushr(self.popl())
|
326
399
|
n -= 1
|
327
400
|
|
328
|
-
def
|
401
|
+
def rotr(self, n: int = 1) -> None:
|
329
402
|
"""Rotate ca arguments right n times."""
|
330
403
|
if self._cnt < 2:
|
331
404
|
return
|
332
405
|
while n > 0:
|
333
|
-
self.
|
406
|
+
self.pushl(self.popr())
|
334
407
|
n -= 1
|
335
408
|
|
336
409
|
def map[U](self, f: Callable[[D], U], /) -> ca[U]:
|
337
410
|
"""Apply function f over contents, returns new `ca` instance.
|
338
411
|
|
339
|
-
|
340
|
-
|
412
|
+
- parameter `f` function of type `f[~D, ~U] -> ca[~U]`
|
413
|
+
- returns a new instance of type `ca[~U]`
|
414
|
+
|
341
415
|
"""
|
342
416
|
return ca(map(f, self))
|
343
417
|
|
344
|
-
def
|
418
|
+
def foldl[L](self, f: Callable[[L, D], L], /, initial: L | None = None) -> L:
|
345
419
|
"""Left fold ca via function and optional initial value.
|
346
420
|
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
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
|
+
|
354
429
|
"""
|
355
430
|
if self._cnt == 0:
|
356
431
|
if initial is None:
|
357
432
|
msg = 'Method foldL called on an empty ca without an initial value.'
|
358
433
|
raise ValueError(msg)
|
359
|
-
|
360
|
-
return initial
|
361
|
-
else:
|
362
|
-
if initial is None:
|
363
|
-
acc = cast(L, self[0]) # in this case D = L
|
364
|
-
for idx in range(1, self._cnt):
|
365
|
-
acc = f(acc, self[idx])
|
366
|
-
return acc
|
367
|
-
else:
|
368
|
-
acc = initial
|
369
|
-
for d in self:
|
370
|
-
acc = f(acc, d)
|
371
|
-
return acc
|
434
|
+
return initial
|
372
435
|
|
373
|
-
|
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:
|
374
448
|
"""Right fold ca via function and optional initial value.
|
375
449
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
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
|
+
|
383
458
|
"""
|
384
459
|
if self._cnt == 0:
|
385
460
|
if initial is None:
|
386
|
-
msg = 'Method
|
461
|
+
msg = 'Method foldr called on an empty ca without an initial value.'
|
387
462
|
raise ValueError(msg)
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
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
|
401
475
|
|
402
476
|
def capacity(self) -> int:
|
403
477
|
"""Returns current capacity of the ca."""
|
@@ -405,25 +479,30 @@ class ca[D](Sequence[D]):
|
|
405
479
|
|
406
480
|
def empty(self) -> None:
|
407
481
|
"""Empty the ca, keep current capacity."""
|
408
|
-
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
|
409
483
|
|
410
|
-
def
|
484
|
+
def fraction_filled(self) -> float:
|
411
485
|
"""Returns fractional capacity of the ca."""
|
412
|
-
return self._cnt/self._cap
|
486
|
+
return self._cnt / self._cap
|
413
487
|
|
414
|
-
def resize(self, minimum_capacity: int=2) -> None:
|
415
|
-
"""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.
|
416
492
|
|
417
|
-
* to just compact the `ca`, do not provide a min_cap
|
418
493
|
"""
|
419
494
|
self._compact_storage_capacity()
|
420
495
|
if (min_cap := minimum_capacity) > self._cap:
|
421
|
-
self._cap, self._data =
|
422
|
-
min_cap, self._data + [None]*(min_cap - self._cap)
|
496
|
+
self._cap, self._data = min_cap, self._data + [None] * (min_cap - self._cap)
|
423
497
|
if self._cnt == 0:
|
424
498
|
self._front, self._rear = 0, self._cap - 1
|
425
499
|
|
500
|
+
|
426
501
|
def CA[D](*ds: D) -> ca[D]:
|
427
|
-
"""Function to produce a `ca` array from a variable number of arguments.
|
428
|
-
return ca(ds)
|
502
|
+
"""Function to produce a `ca` array from a variable number of arguments.
|
429
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,85 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.3
|
2
|
-
Name: dtools.circular-array
|
3
|
-
Version: 3.9.0
|
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
|
-
# Python Circular Array Implementation
|
23
|
-
|
24
|
-
Python module implementing an indexable, double sided,
|
25
|
-
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
|
-
### Overview
|
34
|
-
|
35
|
-
Useful if used directly as an improved version of a Python List or in
|
36
|
-
a "has-a" relationship when implementing other data structures.
|
37
|
-
|
38
|
-
* O(1) pushes and pops either end.
|
39
|
-
* O(1) indexing
|
40
|
-
* now fully supports slicing!
|
41
|
-
|
42
|
-
### Usage
|
43
|
-
|
44
|
-
```python
|
45
|
-
from dtools.circular_array.ca import CA
|
46
|
-
|
47
|
-
ca = CA(1, 2, 3)
|
48
|
-
assert ca.popL() == 1
|
49
|
-
assert ca.popR() == 3
|
50
|
-
ca.pushR(42, 0)
|
51
|
-
ca.pushL(0, 1)
|
52
|
-
assert repr(ca) == 'CA(1, 0, 2, 42, 0)'
|
53
|
-
assert str(ca) == '(|1, 0, 2, 42, 0|)'
|
54
|
-
|
55
|
-
ca = CA(*range(1,11))
|
56
|
-
assert repr(ca) == 'CA(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)'
|
57
|
-
assert str(ca) == '(|1, 2, 3, 4, 5, 6, 7, 8, 9, 10|)'
|
58
|
-
assert len(ca) == 10
|
59
|
-
tup3 = ca.popLT(3)
|
60
|
-
tup4 = ca.popRT(4)
|
61
|
-
assert tup3 == (1, 2, 3)
|
62
|
-
assert tup4 == (10, 9, 8, 7)
|
63
|
-
|
64
|
-
assert ca == CA(4, 5, 6)
|
65
|
-
four, *rest = ca.popFT(1000)
|
66
|
-
assert four == 4
|
67
|
-
assert rest == [5, 6]
|
68
|
-
assert len(ca) == 0
|
69
|
-
|
70
|
-
ca = CA(1, 2, 3)
|
71
|
-
assert ca.popLD(42) == 1
|
72
|
-
assert ca.popRD(42) == 3
|
73
|
-
assert ca.popLD(42) == 2
|
74
|
-
assert ca.popRD(42) == 42
|
75
|
-
assert ca.popLD(42) == 42
|
76
|
-
assert len(ca) == 0
|
77
|
-
```
|
78
|
-
|
79
|
-
---
|
80
|
-
|
81
|
-
[1]: https://pypi.org/project/dtools.circular-array
|
82
|
-
[2]: https://github.com/grscheller/dtools-circular-array
|
83
|
-
[3]: https://grscheller.github.io/dtools-namespace-docs/circular-array/
|
84
|
-
|
85
|
-
|
@@ -1,7 +0,0 @@
|
|
1
|
-
dtools/circular_array/__init__.py,sha256=yHBn_zsWPfiTPmcdYe4XyO6I6tbx9RTMDBukau6zXWg,917
|
2
|
-
dtools/circular_array/ca.py,sha256=qoOjG-m_Z2XlYqRAA4w0HxSUaqz_U3E-7bpzqK-vtQY,15646
|
3
|
-
dtools/circular_array/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
-
dtools_circular_array-3.9.0.dist-info/LICENSE,sha256=csqbZRvA3Nyuav1aszWvswE8CZtaKr-hMjjjcKqms7w,10774
|
5
|
-
dtools_circular_array-3.9.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
|
6
|
-
dtools_circular_array-3.9.0.dist-info/METADATA,sha256=UgiPcUtbURZgznj3dUJjcOB5LDfEQeisr5vpN2PoAa4,2512
|
7
|
-
dtools_circular_array-3.9.0.dist-info/RECORD,,
|
{dtools_circular_array-3.9.0.dist-info → dtools_circular_array-3.10.1.dist-info/licenses}/LICENSE
RENAMED
File without changes
|