pythonic-fp-circulararray 5.3.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.
- pythonic_fp/circulararray/__init__.py +33 -0
- pythonic_fp/circulararray/auto.py +631 -0
- pythonic_fp/circulararray/auto.pyi +103 -0
- pythonic_fp/circulararray/fixed.py +511 -0
- pythonic_fp/circulararray/fixed.pyi +89 -0
- pythonic_fp/circulararray/py.typed +0 -0
- pythonic_fp_circulararray-5.3.0.dist-info/METADATA +57 -0
- pythonic_fp_circulararray-5.3.0.dist-info/RECORD +10 -0
- pythonic_fp_circulararray-5.3.0.dist-info/WHEEL +4 -0
- pythonic_fp_circulararray-5.3.0.dist-info/licenses/LICENSE +176 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Copyright 2024-2025 Geoffrey R. Scheller
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Package implementing stateful circular array data structures.
|
|
16
|
+
|
|
17
|
+
- O(1) pops and pushes either end
|
|
18
|
+
- O(1) indexing and size determination
|
|
19
|
+
- O(1) size determination
|
|
20
|
+
|
|
21
|
+
+-------------------------------------+-------------------------------------------+
|
|
22
|
+
| Module | Description |
|
|
23
|
+
+=====================================+===========================================+
|
|
24
|
+
| **pythonic_fp.circulararray.auto** | Variable storage capacity circular array. |
|
|
25
|
+
+-------------------------------------+-------------------------------------------+
|
|
26
|
+
| **pythonic_fp.circulararray.fixed** | Fixed storage capacity circular array. |
|
|
27
|
+
+-------------------------------------+-------------------------------------------+
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
__author__ = 'Geoffrey R. Scheller'
|
|
32
|
+
__copyright__ = 'Copyright (c) 2023-2025 Geoffrey R. Scheller'
|
|
33
|
+
__license__ = 'Apache License 2.0'
|
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
# Copyright 2023-202 Geoffrey R. Scheller
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Circular array with variable storage capacity.
|
|
16
|
+
|
|
17
|
+
- O(1) pops either end
|
|
18
|
+
- O(1) amortized pushes either end
|
|
19
|
+
- O(1) indexing, fully supports slicing
|
|
20
|
+
- auto-resizing more storage capacity when necessary, manually compatible
|
|
21
|
+
- iterable, can safely mutate while iterators continue iterating over previous state
|
|
22
|
+
- comparisons compare identity before equality, like builtins
|
|
23
|
+
- in boolean context, falsy when empty, otherwise truthy
|
|
24
|
+
- factory function ca produces a variable storage capacity circular array from its arguments
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
28
|
+
from typing import cast, Never, overload, TypeVar
|
|
29
|
+
|
|
30
|
+
__all__ = ['CA', 'ca']
|
|
31
|
+
|
|
32
|
+
I = TypeVar('I')
|
|
33
|
+
T = TypeVar('T')
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CA[I]():
|
|
37
|
+
|
|
38
|
+
L = TypeVar('L')
|
|
39
|
+
R = TypeVar('R')
|
|
40
|
+
U = TypeVar('U')
|
|
41
|
+
|
|
42
|
+
__slots__ = '_items', '_cnt', '_cap', '_front', '_rear'
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
items: Iterable[I] | None = None
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Basically a list that can be grown from both ends
|
|
49
|
+
in O(1) time and space complexity.
|
|
50
|
+
|
|
51
|
+
:param items: optional iterable to initial populate circular array
|
|
52
|
+
:raises TypeError: if items is not Iterable
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
if items is None:
|
|
56
|
+
self._items: list[I | None] = [None, None]
|
|
57
|
+
else:
|
|
58
|
+
self._items = [None] + list(items) + [None]
|
|
59
|
+
self._cap = cap = len(self._items)
|
|
60
|
+
self._cnt = cap - 2
|
|
61
|
+
if cap == 2:
|
|
62
|
+
self._front = 0
|
|
63
|
+
self._rear = 1
|
|
64
|
+
else:
|
|
65
|
+
self._front = 1
|
|
66
|
+
self._rear = cap - 2
|
|
67
|
+
|
|
68
|
+
def _double_storage_capacity(self) -> None:
|
|
69
|
+
if self._front <= self._rear:
|
|
70
|
+
(
|
|
71
|
+
self._items,
|
|
72
|
+
self._cap,
|
|
73
|
+
) = (
|
|
74
|
+
self._items + [None] * self._cap,
|
|
75
|
+
self._cap * 2,
|
|
76
|
+
)
|
|
77
|
+
else:
|
|
78
|
+
(
|
|
79
|
+
self._items,
|
|
80
|
+
self._front,
|
|
81
|
+
self._cap,
|
|
82
|
+
) = (
|
|
83
|
+
self._items[: self._front] + [None]*self._cap + self._items[self._front:],
|
|
84
|
+
self._front + self._cap,
|
|
85
|
+
2*self._cap,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def _compact_storage_capacity(self) -> None:
|
|
89
|
+
match self._cnt:
|
|
90
|
+
case 0:
|
|
91
|
+
(
|
|
92
|
+
self._cap,
|
|
93
|
+
self._front,
|
|
94
|
+
self._rear,
|
|
95
|
+
self._items,
|
|
96
|
+
) = (
|
|
97
|
+
2,
|
|
98
|
+
0,
|
|
99
|
+
1,
|
|
100
|
+
[None, None],
|
|
101
|
+
)
|
|
102
|
+
case 1:
|
|
103
|
+
(
|
|
104
|
+
self._cap,
|
|
105
|
+
self._front,
|
|
106
|
+
self._rear,
|
|
107
|
+
self._items,
|
|
108
|
+
) = (
|
|
109
|
+
3,
|
|
110
|
+
1,
|
|
111
|
+
1,
|
|
112
|
+
[None, self._items[self._front], None],
|
|
113
|
+
)
|
|
114
|
+
case _:
|
|
115
|
+
if self._front <= self._rear:
|
|
116
|
+
(
|
|
117
|
+
self._cap,
|
|
118
|
+
self._front,
|
|
119
|
+
self._rear,
|
|
120
|
+
self._items,
|
|
121
|
+
) = (
|
|
122
|
+
self._cnt + 2,
|
|
123
|
+
1,
|
|
124
|
+
self._cnt,
|
|
125
|
+
[None] + self._items[self._front : self._rear + 1] + [None],
|
|
126
|
+
)
|
|
127
|
+
else:
|
|
128
|
+
(
|
|
129
|
+
self._cap,
|
|
130
|
+
self._front,
|
|
131
|
+
self._rear,
|
|
132
|
+
self._items,
|
|
133
|
+
) = (
|
|
134
|
+
self._cnt + 2,
|
|
135
|
+
1,
|
|
136
|
+
self._cnt,
|
|
137
|
+
[None] + self._items[self._front :] + self._items[: self._rear + 1] + [None],
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def __iter__(self) -> Iterator[I]:
|
|
141
|
+
if self._cnt > 0:
|
|
142
|
+
(
|
|
143
|
+
capacity,
|
|
144
|
+
rear,
|
|
145
|
+
position,
|
|
146
|
+
current_state,
|
|
147
|
+
) = (
|
|
148
|
+
self._cap,
|
|
149
|
+
self._rear,
|
|
150
|
+
self._front,
|
|
151
|
+
self._items.copy(),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
while position != rear:
|
|
155
|
+
yield cast(I, current_state[position])
|
|
156
|
+
position = (position + 1) % capacity
|
|
157
|
+
yield cast(I, current_state[position])
|
|
158
|
+
|
|
159
|
+
def __reversed__(self) -> Iterator[I]:
|
|
160
|
+
if self._cnt > 0:
|
|
161
|
+
(
|
|
162
|
+
capacity,
|
|
163
|
+
front,
|
|
164
|
+
position,
|
|
165
|
+
current_state,
|
|
166
|
+
) = (
|
|
167
|
+
self._cap,
|
|
168
|
+
self._front,
|
|
169
|
+
self._rear,
|
|
170
|
+
self._items.copy(),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
while position != front:
|
|
174
|
+
yield cast(I, current_state[position])
|
|
175
|
+
position = (position - 1) % capacity
|
|
176
|
+
yield cast(I, current_state[position])
|
|
177
|
+
|
|
178
|
+
def __repr__(self) -> str:
|
|
179
|
+
return 'ca(' + ', '.join(map(repr, self)) + ')'
|
|
180
|
+
|
|
181
|
+
def __str__(self) -> str:
|
|
182
|
+
return '(|' + ', '.join(map(str, self)) + '|)'
|
|
183
|
+
|
|
184
|
+
def __bool__(self) -> bool:
|
|
185
|
+
return self._cnt > 0
|
|
186
|
+
|
|
187
|
+
def __len__(self) -> int:
|
|
188
|
+
return self._cnt
|
|
189
|
+
|
|
190
|
+
@overload
|
|
191
|
+
def __getitem__(self, idx: int) -> I: ...
|
|
192
|
+
@overload
|
|
193
|
+
def __getitem__(self, idx: slice) -> "CA[I]": ...
|
|
194
|
+
|
|
195
|
+
def __getitem__(self, idx: int | slice) -> I | "CA[I]":
|
|
196
|
+
if isinstance(idx, slice):
|
|
197
|
+
return CA(list(self)[idx])
|
|
198
|
+
|
|
199
|
+
cnt = self._cnt
|
|
200
|
+
if 0 <= idx < cnt:
|
|
201
|
+
return cast(I, self._items[(self._front + idx) % self._cap])
|
|
202
|
+
|
|
203
|
+
if -cnt <= idx < 0:
|
|
204
|
+
return cast(I, self._items[(self._front + cnt + idx) % self._cap])
|
|
205
|
+
|
|
206
|
+
if cnt == 0:
|
|
207
|
+
msg0 = 'Trying to get a value from an empty CA.'
|
|
208
|
+
raise IndexError(msg0)
|
|
209
|
+
|
|
210
|
+
msg1 = 'Out of bounds: '
|
|
211
|
+
msg2 = f'index = {idx} not between {-cnt} and {cnt - 1} '
|
|
212
|
+
msg3 = 'while getting value from a CA.'
|
|
213
|
+
raise IndexError(msg1 + msg2 + msg3)
|
|
214
|
+
|
|
215
|
+
@overload
|
|
216
|
+
def __setitem__(self, idx: int, vals: I) -> None: ...
|
|
217
|
+
@overload
|
|
218
|
+
def __setitem__(self, idx: slice, vals: Iterable[I]) -> None: ...
|
|
219
|
+
|
|
220
|
+
def __setitem__(self, idx: int | slice, vals: I | Iterable[I]) -> None:
|
|
221
|
+
if isinstance(idx, slice):
|
|
222
|
+
if isinstance(vals, Iterable):
|
|
223
|
+
item_list = list(self)
|
|
224
|
+
item_list[idx] = vals
|
|
225
|
+
_ca = CA(item_list)
|
|
226
|
+
(
|
|
227
|
+
self._items,
|
|
228
|
+
self._cnt,
|
|
229
|
+
self._cap,
|
|
230
|
+
self._front,
|
|
231
|
+
self._rear,
|
|
232
|
+
) = (
|
|
233
|
+
_ca._items,
|
|
234
|
+
_ca._cnt,
|
|
235
|
+
_ca._cap,
|
|
236
|
+
_ca._front,
|
|
237
|
+
_ca._rear,
|
|
238
|
+
)
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
msg = 'must assign iterable to extended slice'
|
|
242
|
+
raise TypeError(msg)
|
|
243
|
+
|
|
244
|
+
cnt = self._cnt
|
|
245
|
+
if 0 <= idx < cnt:
|
|
246
|
+
self._items[(self._front + idx) % self._cap] = cast(I, vals)
|
|
247
|
+
elif -cnt <= idx < 0:
|
|
248
|
+
self._items[(self._front + cnt + idx) % self._cap] = cast(I, vals)
|
|
249
|
+
else:
|
|
250
|
+
if cnt < 1:
|
|
251
|
+
msg0 = 'Trying to index into an empty CA.'
|
|
252
|
+
raise IndexError(msg0)
|
|
253
|
+
msg1 = 'Out of bounds: '
|
|
254
|
+
msg2 = f'index = {idx} not between {-cnt} and {cnt - 1} '
|
|
255
|
+
msg3 = 'while setting value from a CA.'
|
|
256
|
+
raise IndexError(msg1 + msg2 + msg3)
|
|
257
|
+
|
|
258
|
+
@overload
|
|
259
|
+
def __delitem__(self, idx: int) -> None: ...
|
|
260
|
+
@overload
|
|
261
|
+
def __delitem__(self, idx: slice) -> None: ...
|
|
262
|
+
|
|
263
|
+
def __delitem__(self, idx: int | slice) -> None:
|
|
264
|
+
item_list = list(self)
|
|
265
|
+
del item_list[idx]
|
|
266
|
+
_ca = CA(item_list)
|
|
267
|
+
(
|
|
268
|
+
self._items,
|
|
269
|
+
self._cnt,
|
|
270
|
+
self._cap,
|
|
271
|
+
self._front,
|
|
272
|
+
self._rear,
|
|
273
|
+
) = (
|
|
274
|
+
_ca._items,
|
|
275
|
+
_ca._cnt,
|
|
276
|
+
_ca._cap,
|
|
277
|
+
_ca._front,
|
|
278
|
+
_ca._rear,
|
|
279
|
+
)
|
|
280
|
+
del _ca
|
|
281
|
+
|
|
282
|
+
def __eq__(self, other: object) -> bool:
|
|
283
|
+
if self is other:
|
|
284
|
+
return True
|
|
285
|
+
if not isinstance(other, type(self)):
|
|
286
|
+
return False
|
|
287
|
+
|
|
288
|
+
(
|
|
289
|
+
front1,
|
|
290
|
+
count1,
|
|
291
|
+
capacity1,
|
|
292
|
+
front2,
|
|
293
|
+
count2,
|
|
294
|
+
capacity2,
|
|
295
|
+
) = (
|
|
296
|
+
self._front,
|
|
297
|
+
self._cnt,
|
|
298
|
+
self._cap,
|
|
299
|
+
other._front,
|
|
300
|
+
other._cnt,
|
|
301
|
+
other._cap,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
if count1 != count2:
|
|
305
|
+
return False
|
|
306
|
+
|
|
307
|
+
for nn in range(count1):
|
|
308
|
+
if self._items[(front1 + nn) % capacity1] is other._items[(front2 + nn) % capacity2]:
|
|
309
|
+
continue
|
|
310
|
+
if self._items[(front1 + nn) % capacity1] != other._items[(front2 + nn) % capacity2]:
|
|
311
|
+
return False
|
|
312
|
+
return True
|
|
313
|
+
|
|
314
|
+
def pushl(self, *items: I) -> None:
|
|
315
|
+
"""Push items onto left side (front) of circular array.
|
|
316
|
+
|
|
317
|
+
:param items: items pushed onto circular array from left
|
|
318
|
+
|
|
319
|
+
"""
|
|
320
|
+
for item in items:
|
|
321
|
+
if self._cnt == self._cap:
|
|
322
|
+
self._double_storage_capacity()
|
|
323
|
+
(
|
|
324
|
+
self._front,
|
|
325
|
+
self._items[self._front],
|
|
326
|
+
self._cnt,
|
|
327
|
+
) = (
|
|
328
|
+
(self._front - 1) % self._cap,
|
|
329
|
+
item,
|
|
330
|
+
self._cnt + 1,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
def pushr(self, *items: I) -> None:
|
|
334
|
+
"""Push items onto right side (rear) of circular array.
|
|
335
|
+
|
|
336
|
+
:param items: items pushed onto circular array from right
|
|
337
|
+
|
|
338
|
+
"""
|
|
339
|
+
for item in items:
|
|
340
|
+
if self._cnt == self._cap:
|
|
341
|
+
self._double_storage_capacity()
|
|
342
|
+
(
|
|
343
|
+
self._rear,
|
|
344
|
+
self._items[self._rear],
|
|
345
|
+
self._cnt,
|
|
346
|
+
) = (
|
|
347
|
+
(self._rear + 1) % self._cap,
|
|
348
|
+
item,
|
|
349
|
+
self._cnt + 1,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
def popl(self) -> I | Never:
|
|
353
|
+
"""Pop item off left side (front) of circular array.
|
|
354
|
+
|
|
355
|
+
:return: item popped from left side of circular array
|
|
356
|
+
:raises ValueError: when called on an empty circular array
|
|
357
|
+
|
|
358
|
+
"""
|
|
359
|
+
if self._cnt > 1:
|
|
360
|
+
(
|
|
361
|
+
d,
|
|
362
|
+
self._items[self._front],
|
|
363
|
+
self._front,
|
|
364
|
+
self._cnt,
|
|
365
|
+
) = (
|
|
366
|
+
self._items[self._front],
|
|
367
|
+
None,
|
|
368
|
+
(self._front + 1) % self._cap,
|
|
369
|
+
self._cnt - 1,
|
|
370
|
+
)
|
|
371
|
+
elif self._cnt == 1:
|
|
372
|
+
(
|
|
373
|
+
d,
|
|
374
|
+
self._items[self._front],
|
|
375
|
+
self._cnt,
|
|
376
|
+
self._front,
|
|
377
|
+
self._rear,
|
|
378
|
+
) = (
|
|
379
|
+
self._items[self._front],
|
|
380
|
+
None,
|
|
381
|
+
0,
|
|
382
|
+
0,
|
|
383
|
+
self._cap - 1,
|
|
384
|
+
)
|
|
385
|
+
else:
|
|
386
|
+
msg = 'Method popl called on an empty CA'
|
|
387
|
+
raise ValueError(msg)
|
|
388
|
+
return cast(I, d)
|
|
389
|
+
|
|
390
|
+
def popr(self) -> I | Never:
|
|
391
|
+
"""Pop item off right side (rear) of circular array.
|
|
392
|
+
|
|
393
|
+
:return: item popped from right side of circular array
|
|
394
|
+
:raises ValueError: when called on an empty circular array
|
|
395
|
+
|
|
396
|
+
"""
|
|
397
|
+
if self._cnt > 1:
|
|
398
|
+
(
|
|
399
|
+
d,
|
|
400
|
+
self._items[self._rear],
|
|
401
|
+
self._rear,
|
|
402
|
+
self._cnt,
|
|
403
|
+
) = (
|
|
404
|
+
self._items[self._rear],
|
|
405
|
+
None,
|
|
406
|
+
(self._rear - 1) % self._cap,
|
|
407
|
+
self._cnt - 1,
|
|
408
|
+
)
|
|
409
|
+
elif self._cnt == 1:
|
|
410
|
+
(
|
|
411
|
+
d,
|
|
412
|
+
self._items[self._front],
|
|
413
|
+
self._cnt,
|
|
414
|
+
self._front,
|
|
415
|
+
self._rear,
|
|
416
|
+
) = (
|
|
417
|
+
self._items[self._front],
|
|
418
|
+
None,
|
|
419
|
+
0,
|
|
420
|
+
0,
|
|
421
|
+
self._cap - 1,
|
|
422
|
+
)
|
|
423
|
+
else:
|
|
424
|
+
msg = 'Method popr called on an empty CA'
|
|
425
|
+
raise ValueError(msg)
|
|
426
|
+
return cast(I, d)
|
|
427
|
+
|
|
428
|
+
def popld(self, default: I) -> I:
|
|
429
|
+
"""Pop one item from left side of the circular array, provide
|
|
430
|
+
a mandatory default value. "Safe" version of popl.
|
|
431
|
+
|
|
432
|
+
:param default: item returned if circular array is empty
|
|
433
|
+
:return: item popped from left side or default item if empty
|
|
434
|
+
|
|
435
|
+
"""
|
|
436
|
+
try:
|
|
437
|
+
return self.popl()
|
|
438
|
+
except ValueError:
|
|
439
|
+
return default
|
|
440
|
+
|
|
441
|
+
def poprd(self, default: I) -> I:
|
|
442
|
+
"""Pop one item from right side of the circular array, provide
|
|
443
|
+
a mandatory default value. "Safe" version of popr.
|
|
444
|
+
|
|
445
|
+
:param default: item returned if circular array is empty
|
|
446
|
+
:return: item popped from right side or default item if empty
|
|
447
|
+
|
|
448
|
+
"""
|
|
449
|
+
try:
|
|
450
|
+
return self.popr()
|
|
451
|
+
except ValueError:
|
|
452
|
+
return default
|
|
453
|
+
|
|
454
|
+
def poplt(self, maximum: int) -> tuple[I, ...]:
|
|
455
|
+
"""Pop multiple items from left side of circular array.
|
|
456
|
+
|
|
457
|
+
:param maximum: maximum number of items to pop, may pop less if not enough items
|
|
458
|
+
:return: items in the order popped, left to right
|
|
459
|
+
|
|
460
|
+
"""
|
|
461
|
+
item_list: list[I] = []
|
|
462
|
+
|
|
463
|
+
while maximum > 0:
|
|
464
|
+
try:
|
|
465
|
+
item_list.append(self.popl())
|
|
466
|
+
except ValueError:
|
|
467
|
+
break
|
|
468
|
+
else:
|
|
469
|
+
maximum -= 1
|
|
470
|
+
|
|
471
|
+
return tuple(item_list)
|
|
472
|
+
|
|
473
|
+
def poprt(self, maximum: int) -> tuple[I, ...]:
|
|
474
|
+
"""Pop multiple items from right side of circular array.
|
|
475
|
+
|
|
476
|
+
:param maximum: maximum number of items to pop, may pop less if not enough items
|
|
477
|
+
:return: items in the order popped, right to left
|
|
478
|
+
|
|
479
|
+
"""
|
|
480
|
+
item_list: list[I] = []
|
|
481
|
+
while maximum > 0:
|
|
482
|
+
try:
|
|
483
|
+
item_list.append(self.popr())
|
|
484
|
+
except ValueError:
|
|
485
|
+
break
|
|
486
|
+
else:
|
|
487
|
+
maximum -= 1
|
|
488
|
+
return tuple(item_list)
|
|
489
|
+
|
|
490
|
+
def rotl(self, n: int = 1) -> None:
|
|
491
|
+
"""Rotate items left.
|
|
492
|
+
|
|
493
|
+
:param n: number of times to shift elements to the left
|
|
494
|
+
|
|
495
|
+
"""
|
|
496
|
+
if self._cnt < 2:
|
|
497
|
+
return
|
|
498
|
+
for _ in range(n, 0, -1):
|
|
499
|
+
self.pushr(self.popl())
|
|
500
|
+
|
|
501
|
+
def rotr(self, n: int = 1) -> None:
|
|
502
|
+
"""Rotate items right.
|
|
503
|
+
|
|
504
|
+
:param n: number of times to shift elements to the right
|
|
505
|
+
|
|
506
|
+
"""
|
|
507
|
+
if self._cnt < 2:
|
|
508
|
+
return
|
|
509
|
+
for _ in range(n, 0, -1):
|
|
510
|
+
self.pushl(self.popr())
|
|
511
|
+
|
|
512
|
+
def map[U](self, f: Callable[[I], U]) -> "CA[U]":
|
|
513
|
+
"""Apply function f over the circular array's contents,
|
|
514
|
+
|
|
515
|
+
:param f: callable from type I to type U
|
|
516
|
+
:return: new circular array instance
|
|
517
|
+
|
|
518
|
+
"""
|
|
519
|
+
return CA(map(f, self))
|
|
520
|
+
|
|
521
|
+
def foldl[L](self, f: Callable[[L, I], L], start: L | None = None) -> L | Never:
|
|
522
|
+
"""Fold left with a function and optional starting item.
|
|
523
|
+
|
|
524
|
+
:param f: first argument to f is for the accumulator
|
|
525
|
+
:param start: optional starting item
|
|
526
|
+
:return: reduced value produced by the left fold
|
|
527
|
+
:raises ValueError: when circular array empty and no starting item given
|
|
528
|
+
|
|
529
|
+
"""
|
|
530
|
+
if self._cnt == 0:
|
|
531
|
+
if start is None:
|
|
532
|
+
msg = 'Method foldl called on an empty CA without a start item.'
|
|
533
|
+
raise ValueError(msg)
|
|
534
|
+
return start
|
|
535
|
+
|
|
536
|
+
if start is None:
|
|
537
|
+
acc = cast(L, self[0]) # in this case D = L
|
|
538
|
+
for idx in range(1, self._cnt):
|
|
539
|
+
acc = f(acc, self[idx])
|
|
540
|
+
return acc
|
|
541
|
+
|
|
542
|
+
acc = start
|
|
543
|
+
for d in self:
|
|
544
|
+
acc = f(acc, d)
|
|
545
|
+
return acc
|
|
546
|
+
|
|
547
|
+
def foldr[R](self, f: Callable[[I, R], R], start: R | None = None) -> R | Never:
|
|
548
|
+
"""Fold right with a function and an optional starting item.
|
|
549
|
+
|
|
550
|
+
:param f: second argument to f is for the accumulator
|
|
551
|
+
:param start: optional starting item
|
|
552
|
+
:return: reduced value produced by the right fold
|
|
553
|
+
:raises ValueError: when circular array empty and no starting item given
|
|
554
|
+
|
|
555
|
+
"""
|
|
556
|
+
if self._cnt == 0:
|
|
557
|
+
if start is None:
|
|
558
|
+
msg = 'Method foldr called on empty CA without initial value.'
|
|
559
|
+
raise ValueError(msg)
|
|
560
|
+
return start
|
|
561
|
+
|
|
562
|
+
if start is None:
|
|
563
|
+
acc = cast(R, self[-1]) # in this case D = R
|
|
564
|
+
for idx in range(self._cnt - 2, -1, -1):
|
|
565
|
+
acc = f(self[idx], acc)
|
|
566
|
+
return acc
|
|
567
|
+
|
|
568
|
+
acc = start
|
|
569
|
+
for d in reversed(self):
|
|
570
|
+
acc = f(d, acc)
|
|
571
|
+
return acc
|
|
572
|
+
|
|
573
|
+
def capacity(self) -> int:
|
|
574
|
+
"""Return current storage capacity of the circular array.
|
|
575
|
+
|
|
576
|
+
:return: current storage capacity
|
|
577
|
+
|
|
578
|
+
"""
|
|
579
|
+
return self._cap
|
|
580
|
+
|
|
581
|
+
def empty(self) -> None:
|
|
582
|
+
"""Empty the circular array, keep current storage capacity."""
|
|
583
|
+
(
|
|
584
|
+
self._items,
|
|
585
|
+
self._front,
|
|
586
|
+
self._rear,
|
|
587
|
+
self._cnt,
|
|
588
|
+
) = (
|
|
589
|
+
[None] * self._cap,
|
|
590
|
+
0,
|
|
591
|
+
self._cap - 1,
|
|
592
|
+
0,
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
def fraction_filled(self) -> float:
|
|
596
|
+
"""Find fraction of the storage capacity which is filled.
|
|
597
|
+
|
|
598
|
+
:return: the ratio count/capacity
|
|
599
|
+
|
|
600
|
+
"""
|
|
601
|
+
return self._cnt / self._cap
|
|
602
|
+
|
|
603
|
+
def resize(self, minimum_capacity: int = 2) -> None:
|
|
604
|
+
"""Compact circular array and, if necessary, resize to a minimum
|
|
605
|
+
storage capacity. To just compact the circular array, do not
|
|
606
|
+
provide ``minimum_capacity``.
|
|
607
|
+
|
|
608
|
+
:param minimum_capacity: minimum storage capacity to compact the circular array
|
|
609
|
+
|
|
610
|
+
"""
|
|
611
|
+
self._compact_storage_capacity()
|
|
612
|
+
if (min_cap := minimum_capacity) > self._cap:
|
|
613
|
+
(
|
|
614
|
+
self._cap,
|
|
615
|
+
self._items,
|
|
616
|
+
) = (
|
|
617
|
+
min_cap,
|
|
618
|
+
self._items + [None] * (min_cap - self._cap),
|
|
619
|
+
)
|
|
620
|
+
if self._cnt == 0:
|
|
621
|
+
self._front, self._rear = 0, self._cap - 1
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
def ca[T](*items: T) -> CA[T]:
|
|
625
|
+
"""Produce circular array from a variable number of arguments.
|
|
626
|
+
|
|
627
|
+
:param items: initial items for a new circular array
|
|
628
|
+
:return: new variable storage capacity circular array
|
|
629
|
+
|
|
630
|
+
"""
|
|
631
|
+
return CA(items)
|