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,103 @@
|
|
|
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
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
16
|
+
from typing import Never, overload, TypeVar
|
|
17
|
+
|
|
18
|
+
__all__ = ['CA', 'ca']
|
|
19
|
+
|
|
20
|
+
I = TypeVar('I')
|
|
21
|
+
T = TypeVar('T')
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CA[I]():
|
|
25
|
+
|
|
26
|
+
L = TypeVar('L')
|
|
27
|
+
R = TypeVar('R')
|
|
28
|
+
U = TypeVar('U')
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
items: Iterable[I] | None = None
|
|
33
|
+
) -> None: ...
|
|
34
|
+
|
|
35
|
+
def _double_storage_capacity(self) -> None: ...
|
|
36
|
+
|
|
37
|
+
def _compact_storage_capacity(self) -> None: ...
|
|
38
|
+
|
|
39
|
+
def __iter__(self) -> Iterator[I]: ...
|
|
40
|
+
|
|
41
|
+
def __reversed__(self) -> Iterator[I]: ...
|
|
42
|
+
|
|
43
|
+
def __repr__(self) -> str: ...
|
|
44
|
+
|
|
45
|
+
def __str__(self) -> str: ...
|
|
46
|
+
|
|
47
|
+
def __bool__(self) -> bool: ...
|
|
48
|
+
|
|
49
|
+
def __len__(self) -> int: ...
|
|
50
|
+
|
|
51
|
+
@overload
|
|
52
|
+
def __getitem__(self, idx: int) -> I: ...
|
|
53
|
+
@overload
|
|
54
|
+
def __getitem__(self, idx: slice) -> CA[I]: ...
|
|
55
|
+
|
|
56
|
+
@overload
|
|
57
|
+
def __setitem__(self, idx: int, vals: I) -> None: ...
|
|
58
|
+
@overload
|
|
59
|
+
def __setitem__(self, idx: slice, vals: Iterable[I]) -> None: ...
|
|
60
|
+
|
|
61
|
+
@overload
|
|
62
|
+
def __delitem__(self, idx: int) -> None: ...
|
|
63
|
+
@overload
|
|
64
|
+
def __delitem__(self, idx: slice) -> None: ...
|
|
65
|
+
|
|
66
|
+
def __eq__(self, other: object) -> bool: ...
|
|
67
|
+
|
|
68
|
+
def pushl(self, *items: I) -> None: ...
|
|
69
|
+
|
|
70
|
+
def pushr(self, *items: I) -> None: ...
|
|
71
|
+
|
|
72
|
+
def popl(self) -> I | Never: ...
|
|
73
|
+
|
|
74
|
+
def popr(self) -> I | Never: ...
|
|
75
|
+
|
|
76
|
+
def popld(self, default: I) -> I: ...
|
|
77
|
+
|
|
78
|
+
def poprd(self, default: I) -> I: ...
|
|
79
|
+
|
|
80
|
+
def poplt(self, maximum: int) -> tuple[I, ...]: ...
|
|
81
|
+
|
|
82
|
+
def poprt(self, maximum: int) -> tuple[I, ...]: ...
|
|
83
|
+
|
|
84
|
+
def rotl(self, n: int = 1) -> None: ...
|
|
85
|
+
|
|
86
|
+
def rotr(self, n: int = 1) -> None: ...
|
|
87
|
+
|
|
88
|
+
def map[U](self, f: Callable[[I], U]) -> CA[U]: ...
|
|
89
|
+
|
|
90
|
+
def foldl[L](self, f: Callable[[L, I], L], start: L | None = None) -> L | Never: ...
|
|
91
|
+
|
|
92
|
+
def foldr[R](self, f: Callable[[I, R], R], start: R | None = None) -> R | Never: ...
|
|
93
|
+
|
|
94
|
+
def capacity(self) -> int: ...
|
|
95
|
+
|
|
96
|
+
def empty(self) -> None: ...
|
|
97
|
+
|
|
98
|
+
def fraction_filled(self) -> float: ...
|
|
99
|
+
|
|
100
|
+
def resize(self, minimum_capacity: int = 2) -> None: ...
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def ca[T](*items: T) -> CA[T]: ...
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
# Copyright 2023-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
|
+
"""Circular array with fixed storage capacity.
|
|
16
|
+
|
|
17
|
+
- O(1) pops and pushes either end
|
|
18
|
+
- O(1) indexing, does not support slicing
|
|
19
|
+
- fixed total storage capacity
|
|
20
|
+
- iterable, can safely mutate while iterators continue iterating over previous state
|
|
21
|
+
- comparisons compare identity before equality, like builtins
|
|
22
|
+
- in boolean context, falsy when either empty or full, otherwise truthy
|
|
23
|
+
- factory function caf produces a fixed storage capacity circular array from its arguments
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
27
|
+
from typing import cast, Final, Never, TypeVar
|
|
28
|
+
|
|
29
|
+
__all__ = ['CAF', 'caf']
|
|
30
|
+
|
|
31
|
+
I = TypeVar('I')
|
|
32
|
+
T = TypeVar('T')
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CAF[I]():
|
|
36
|
+
|
|
37
|
+
L = TypeVar('L')
|
|
38
|
+
R = TypeVar('R')
|
|
39
|
+
U = TypeVar('U')
|
|
40
|
+
|
|
41
|
+
__slots__ = '_items', '_cnt', '_cap', '_front', '_rear'
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
items: Iterable[I] | None = None,
|
|
46
|
+
capacity: int = 2
|
|
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
|
+
:param capacity: fixed storage capacity of circular array
|
|
53
|
+
:raises TypeError: if items is not Iterable
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
capacity = max(2, capacity)
|
|
57
|
+
if items is None:
|
|
58
|
+
self._items: list[I | None] = [None]*capacity
|
|
59
|
+
count = 0
|
|
60
|
+
else:
|
|
61
|
+
dlist: list[I | None] = list(items)
|
|
62
|
+
count = len(dlist)
|
|
63
|
+
capacity = max(count, capacity)
|
|
64
|
+
self._items = dlist + [None]*(capacity - count)
|
|
65
|
+
self._cap: Final[int] = capacity
|
|
66
|
+
self._cnt = count
|
|
67
|
+
if count == 0:
|
|
68
|
+
self._front = 0
|
|
69
|
+
self._rear = capacity - 1
|
|
70
|
+
else:
|
|
71
|
+
self._front = 0
|
|
72
|
+
self._rear = count - 1
|
|
73
|
+
|
|
74
|
+
def __iter__(self) -> Iterator[I]:
|
|
75
|
+
if self._cnt > 0:
|
|
76
|
+
(
|
|
77
|
+
capacity,
|
|
78
|
+
rear,
|
|
79
|
+
position,
|
|
80
|
+
current_state,
|
|
81
|
+
) = (
|
|
82
|
+
self._cap,
|
|
83
|
+
self._rear,
|
|
84
|
+
self._front,
|
|
85
|
+
self._items.copy(),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
while position != rear:
|
|
89
|
+
yield cast(I, current_state[position])
|
|
90
|
+
position = (position + 1) % capacity
|
|
91
|
+
yield cast(I, current_state[position])
|
|
92
|
+
|
|
93
|
+
def __reversed__(self) -> Iterator[I]:
|
|
94
|
+
if self._cnt > 0:
|
|
95
|
+
(
|
|
96
|
+
capacity,
|
|
97
|
+
front,
|
|
98
|
+
position,
|
|
99
|
+
current_state,
|
|
100
|
+
) = (
|
|
101
|
+
self._cap,
|
|
102
|
+
self._front,
|
|
103
|
+
self._rear,
|
|
104
|
+
self._items.copy(),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
while position != front:
|
|
108
|
+
yield cast(I, current_state[position])
|
|
109
|
+
position = (position - 1) % capacity
|
|
110
|
+
yield cast(I, current_state[position])
|
|
111
|
+
|
|
112
|
+
def __repr__(self) -> str:
|
|
113
|
+
return 'caf(' + ', '.join(map(repr, self)) + ')'
|
|
114
|
+
|
|
115
|
+
def __str__(self) -> str:
|
|
116
|
+
return '(|' + ', '.join(map(str, self)) + '|)'
|
|
117
|
+
|
|
118
|
+
def __bool__(self) -> bool:
|
|
119
|
+
return 0 < self._cnt < self._cap
|
|
120
|
+
|
|
121
|
+
def __len__(self) -> int:
|
|
122
|
+
return self._cnt
|
|
123
|
+
|
|
124
|
+
def __getitem__(self, idx: int) -> I:
|
|
125
|
+
cnt = self._cnt
|
|
126
|
+
if 0 <= idx < cnt:
|
|
127
|
+
return cast(I, self._items[(self._front + idx) % self._cap])
|
|
128
|
+
|
|
129
|
+
if -cnt <= idx < 0:
|
|
130
|
+
return cast(I, self._items[(self._front + cnt + idx) % self._cap])
|
|
131
|
+
|
|
132
|
+
if cnt == 0:
|
|
133
|
+
msg0 = 'Trying to get a value from an empty CAF.'
|
|
134
|
+
raise IndexError(msg0)
|
|
135
|
+
|
|
136
|
+
msg1 = 'Out of bounds: '
|
|
137
|
+
msg2 = f'index = {idx} not between {-cnt} and {cnt - 1} '
|
|
138
|
+
msg3 = 'while getting value from a CAF.'
|
|
139
|
+
raise IndexError(msg1 + msg2 + msg3)
|
|
140
|
+
|
|
141
|
+
def __setitem__(self, idx: int, val: I) -> None:
|
|
142
|
+
cnt = self._cnt
|
|
143
|
+
if 0 <= idx < cnt:
|
|
144
|
+
self._items[(self._front + idx) % self._cap] = val
|
|
145
|
+
elif -cnt <= idx < 0:
|
|
146
|
+
self._items[(self._front + cnt + idx) % self._cap] = val
|
|
147
|
+
else:
|
|
148
|
+
if cnt < 1:
|
|
149
|
+
msg0 = 'Trying to index into an empty CAF.'
|
|
150
|
+
raise IndexError(msg0)
|
|
151
|
+
msg1 = 'Out of bounds: '
|
|
152
|
+
msg2 = f'index = {idx} not between {-cnt} and {cnt - 1} '
|
|
153
|
+
msg3 = 'while setting value from a CAF.'
|
|
154
|
+
raise IndexError(msg1 + msg2 + msg3)
|
|
155
|
+
|
|
156
|
+
def __delitem__(self, idx: int) -> None:
|
|
157
|
+
item_list = list(self)
|
|
158
|
+
del item_list[idx]
|
|
159
|
+
_ca = CAF(item_list, self._cap)
|
|
160
|
+
(
|
|
161
|
+
self._items,
|
|
162
|
+
self._cnt,
|
|
163
|
+
self._front,
|
|
164
|
+
self._rear,
|
|
165
|
+
) = (
|
|
166
|
+
_ca._items,
|
|
167
|
+
_ca._cnt,
|
|
168
|
+
_ca._front,
|
|
169
|
+
_ca._rear,
|
|
170
|
+
)
|
|
171
|
+
del _ca
|
|
172
|
+
|
|
173
|
+
def __eq__(self, other: object) -> bool:
|
|
174
|
+
if self is other:
|
|
175
|
+
return True
|
|
176
|
+
if not isinstance(other, type(self)):
|
|
177
|
+
return False
|
|
178
|
+
|
|
179
|
+
(
|
|
180
|
+
front1,
|
|
181
|
+
count1,
|
|
182
|
+
capacity1,
|
|
183
|
+
front2,
|
|
184
|
+
count2,
|
|
185
|
+
capacity2,
|
|
186
|
+
) = (
|
|
187
|
+
self._front,
|
|
188
|
+
self._cnt,
|
|
189
|
+
self._cap,
|
|
190
|
+
other._front,
|
|
191
|
+
other._cnt,
|
|
192
|
+
other._cap,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if count1 != count2:
|
|
196
|
+
return False
|
|
197
|
+
|
|
198
|
+
for nn in range(count1):
|
|
199
|
+
if self._items[(front1 + nn) % capacity1] is other._items[(front2 + nn) % capacity2]:
|
|
200
|
+
continue
|
|
201
|
+
if self._items[(front1 + nn) % capacity1] != other._items[(front2 + nn) % capacity2]:
|
|
202
|
+
return False
|
|
203
|
+
return True
|
|
204
|
+
|
|
205
|
+
def pushl(self, item: I) -> None:
|
|
206
|
+
"""Push single item onto left side (front) of circular array.
|
|
207
|
+
|
|
208
|
+
:param item: single item pushed onto circular array from left
|
|
209
|
+
:raises ValueError: when called on a full CAF
|
|
210
|
+
|
|
211
|
+
"""
|
|
212
|
+
if self._cnt == self._cap:
|
|
213
|
+
msg = 'Method pushl called on a full CAF'
|
|
214
|
+
raise ValueError(msg)
|
|
215
|
+
|
|
216
|
+
(
|
|
217
|
+
self._front,
|
|
218
|
+
self._items[self._front],
|
|
219
|
+
self._cnt,
|
|
220
|
+
) = (
|
|
221
|
+
(self._front - 1) % self._cap,
|
|
222
|
+
item,
|
|
223
|
+
self._cnt + 1,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def pushr(self, item: I) -> None:
|
|
227
|
+
"""Push a single item onto right side (rear) of circular array.
|
|
228
|
+
|
|
229
|
+
:param item: single item pushed onto circular array from right
|
|
230
|
+
:raises ValueError: when called on a full fixed storage capacity circular array
|
|
231
|
+
|
|
232
|
+
"""
|
|
233
|
+
if self._cnt == self._cap:
|
|
234
|
+
msg = 'Method pushr called on a full CAF'
|
|
235
|
+
raise ValueError(msg)
|
|
236
|
+
|
|
237
|
+
(
|
|
238
|
+
self._rear,
|
|
239
|
+
self._items[self._rear],
|
|
240
|
+
self._cnt,
|
|
241
|
+
) = (
|
|
242
|
+
(self._rear + 1) % self._cap,
|
|
243
|
+
item,
|
|
244
|
+
self._cnt + 1,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
def popl(self) -> I | Never:
|
|
248
|
+
"""Pop item off left side (front) of circular array.
|
|
249
|
+
|
|
250
|
+
:return: item popped from left side of circular array
|
|
251
|
+
:raises ValueError: when called on an empty circular array
|
|
252
|
+
|
|
253
|
+
"""
|
|
254
|
+
if self._cnt > 1:
|
|
255
|
+
(
|
|
256
|
+
d,
|
|
257
|
+
self._items[self._front],
|
|
258
|
+
self._front,
|
|
259
|
+
self._cnt,
|
|
260
|
+
) = (
|
|
261
|
+
self._items[self._front],
|
|
262
|
+
None,
|
|
263
|
+
(self._front + 1) % self._cap,
|
|
264
|
+
self._cnt - 1,
|
|
265
|
+
)
|
|
266
|
+
elif self._cnt == 1:
|
|
267
|
+
(
|
|
268
|
+
d,
|
|
269
|
+
self._items[self._front],
|
|
270
|
+
self._cnt,
|
|
271
|
+
self._front,
|
|
272
|
+
self._rear,
|
|
273
|
+
) = (
|
|
274
|
+
self._items[self._front],
|
|
275
|
+
None,
|
|
276
|
+
0,
|
|
277
|
+
0,
|
|
278
|
+
self._cap - 1,
|
|
279
|
+
)
|
|
280
|
+
else:
|
|
281
|
+
msg = 'Method popl called on an empty CAF'
|
|
282
|
+
raise ValueError(msg)
|
|
283
|
+
return cast(I, d)
|
|
284
|
+
|
|
285
|
+
def popr(self) -> I | Never:
|
|
286
|
+
"""Pop item off right side (rear) of circular array.
|
|
287
|
+
|
|
288
|
+
:return: item popped from right side of circular array
|
|
289
|
+
:raises ValueError: when called on an empty circular array
|
|
290
|
+
|
|
291
|
+
"""
|
|
292
|
+
if self._cnt > 1:
|
|
293
|
+
(
|
|
294
|
+
d,
|
|
295
|
+
self._items[self._rear],
|
|
296
|
+
self._rear,
|
|
297
|
+
self._cnt,
|
|
298
|
+
) = (
|
|
299
|
+
self._items[self._rear],
|
|
300
|
+
None,
|
|
301
|
+
(self._rear - 1) % self._cap,
|
|
302
|
+
self._cnt - 1,
|
|
303
|
+
)
|
|
304
|
+
elif self._cnt == 1:
|
|
305
|
+
(
|
|
306
|
+
d,
|
|
307
|
+
self._items[self._front],
|
|
308
|
+
self._cnt,
|
|
309
|
+
self._front,
|
|
310
|
+
self._rear,
|
|
311
|
+
) = (
|
|
312
|
+
self._items[self._front],
|
|
313
|
+
None,
|
|
314
|
+
0,
|
|
315
|
+
0,
|
|
316
|
+
self._cap - 1,
|
|
317
|
+
)
|
|
318
|
+
else:
|
|
319
|
+
msg = 'Method popr called on an empty CAF'
|
|
320
|
+
raise ValueError(msg)
|
|
321
|
+
return cast(I, d)
|
|
322
|
+
|
|
323
|
+
def popld(self, default: I) -> I:
|
|
324
|
+
"""Pop one item from left side of the circular array, provide
|
|
325
|
+
a mandatory default value. "Safe" version of popl.
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
Pop one item from left side of the circular array, provide
|
|
329
|
+
a mandatory default value. "Safe" version of popl.
|
|
330
|
+
|
|
331
|
+
:param default: item returned if circular array is empty
|
|
332
|
+
:return: item popped from left side or default item if empty
|
|
333
|
+
|
|
334
|
+
"""
|
|
335
|
+
try:
|
|
336
|
+
return self.popl()
|
|
337
|
+
except ValueError:
|
|
338
|
+
return default
|
|
339
|
+
|
|
340
|
+
def poprd(self, default: I) -> I:
|
|
341
|
+
"""Pop one item from right side of the circular array, provide
|
|
342
|
+
a mandatory default value. "Safe" version of popr.
|
|
343
|
+
|
|
344
|
+
:param default: item returned if circular array is empty
|
|
345
|
+
:return: item popped from right side or default item if empty
|
|
346
|
+
|
|
347
|
+
"""
|
|
348
|
+
try:
|
|
349
|
+
return self.popr()
|
|
350
|
+
except ValueError:
|
|
351
|
+
return default
|
|
352
|
+
|
|
353
|
+
def poplt(self, maximum: int) -> tuple[I, ...]:
|
|
354
|
+
"""Pop multiple items from left side of circular array.
|
|
355
|
+
|
|
356
|
+
:param maximum: maximum number of items to pop, may pop less if not enough items
|
|
357
|
+
:return: items in the order popped, left to right
|
|
358
|
+
|
|
359
|
+
"""
|
|
360
|
+
item_list: list[I] = []
|
|
361
|
+
|
|
362
|
+
while maximum > 0:
|
|
363
|
+
try:
|
|
364
|
+
item_list.append(self.popl())
|
|
365
|
+
except ValueError:
|
|
366
|
+
break
|
|
367
|
+
else:
|
|
368
|
+
maximum -= 1
|
|
369
|
+
|
|
370
|
+
return tuple(item_list)
|
|
371
|
+
|
|
372
|
+
def poprt(self, maximum: int) -> tuple[I, ...]:
|
|
373
|
+
"""Pop multiple items from right side of circular array.
|
|
374
|
+
|
|
375
|
+
:param maximum: maximum number of items to pop, may pop less if not enough items
|
|
376
|
+
:return: items in the order popped, right to left
|
|
377
|
+
|
|
378
|
+
"""
|
|
379
|
+
item_list: list[I] = []
|
|
380
|
+
while maximum > 0:
|
|
381
|
+
try:
|
|
382
|
+
item_list.append(self.popr())
|
|
383
|
+
except ValueError:
|
|
384
|
+
break
|
|
385
|
+
else:
|
|
386
|
+
maximum -= 1
|
|
387
|
+
return tuple(item_list)
|
|
388
|
+
|
|
389
|
+
def rotl(self, n: int = 1) -> None:
|
|
390
|
+
"""Rotate items left.
|
|
391
|
+
|
|
392
|
+
:param n: number of times to shift elements to the left
|
|
393
|
+
|
|
394
|
+
"""
|
|
395
|
+
if self._cnt < 2:
|
|
396
|
+
return
|
|
397
|
+
for _ in range(n, 0, -1):
|
|
398
|
+
self.pushr(self.popl())
|
|
399
|
+
|
|
400
|
+
def rotr(self, n: int = 1) -> None:
|
|
401
|
+
"""Rotate items right.
|
|
402
|
+
|
|
403
|
+
:param n: number of times to shift elements to the right
|
|
404
|
+
|
|
405
|
+
"""
|
|
406
|
+
if self._cnt < 2:
|
|
407
|
+
return
|
|
408
|
+
for _ in range(n, 0, -1):
|
|
409
|
+
self.pushl(self.popr())
|
|
410
|
+
|
|
411
|
+
def map[U](self, f: Callable[[I], U]) -> "CAF[U]":
|
|
412
|
+
"""Apply function f over the circular array's contents,
|
|
413
|
+
|
|
414
|
+
:param f: callable from type I to type U
|
|
415
|
+
:return: new fixed circular array instance
|
|
416
|
+
|
|
417
|
+
"""
|
|
418
|
+
return CAF(map(f, self), self._cap)
|
|
419
|
+
|
|
420
|
+
def foldl[L](self, f: Callable[[L, I], L], start: L | None = None) -> L | Never:
|
|
421
|
+
"""Fold left with a function and optional stating item.
|
|
422
|
+
|
|
423
|
+
:param f: first argument to f is for the accumulator
|
|
424
|
+
:param start: optional starting item
|
|
425
|
+
:return: reduced value produced by the left fold
|
|
426
|
+
:raises ValueError: when circular array empty and no starting item given
|
|
427
|
+
|
|
428
|
+
"""
|
|
429
|
+
if self._cnt == 0:
|
|
430
|
+
if start is None:
|
|
431
|
+
msg = 'Method foldl called on an empty CAF without a start item.'
|
|
432
|
+
raise ValueError(msg)
|
|
433
|
+
return start
|
|
434
|
+
|
|
435
|
+
if start is None:
|
|
436
|
+
acc = cast(L, self[0]) # in this case D = L
|
|
437
|
+
for idx in range(1, self._cnt):
|
|
438
|
+
acc = f(acc, self[idx])
|
|
439
|
+
return acc
|
|
440
|
+
|
|
441
|
+
acc = start
|
|
442
|
+
for d in self:
|
|
443
|
+
acc = f(acc, d)
|
|
444
|
+
return acc
|
|
445
|
+
|
|
446
|
+
def foldr[R](self, f: Callable[[I, R], R], start: R | None = None) -> R | Never:
|
|
447
|
+
"""Fold right with a function and an optional starting item.
|
|
448
|
+
|
|
449
|
+
:param f: second argument to f is for the accumulator
|
|
450
|
+
:param start: optional starting item
|
|
451
|
+
:return: reduced value produced by the right fold
|
|
452
|
+
:raises ValueError: when circular array empty and no starting item given
|
|
453
|
+
|
|
454
|
+
"""
|
|
455
|
+
if self._cnt == 0:
|
|
456
|
+
if start is None:
|
|
457
|
+
msg = 'Method foldr called on empty CAF without initial value.'
|
|
458
|
+
raise ValueError(msg)
|
|
459
|
+
return start
|
|
460
|
+
|
|
461
|
+
if start is None:
|
|
462
|
+
acc = cast(R, self[-1]) # in this case D = R
|
|
463
|
+
for idx in range(self._cnt - 2, -1, -1):
|
|
464
|
+
acc = f(self[idx], acc)
|
|
465
|
+
return acc
|
|
466
|
+
|
|
467
|
+
acc = start
|
|
468
|
+
for d in reversed(self):
|
|
469
|
+
acc = f(d, acc)
|
|
470
|
+
return acc
|
|
471
|
+
|
|
472
|
+
def capacity(self) -> int:
|
|
473
|
+
"""Return fixed storage capacity of the circular array.
|
|
474
|
+
|
|
475
|
+
:return: fixed storage capacity
|
|
476
|
+
|
|
477
|
+
"""
|
|
478
|
+
return self._cap
|
|
479
|
+
|
|
480
|
+
def empty(self) -> None:
|
|
481
|
+
"""Empty the circular array."""
|
|
482
|
+
(
|
|
483
|
+
self._items,
|
|
484
|
+
self._front,
|
|
485
|
+
self._rear,
|
|
486
|
+
self._cnt,
|
|
487
|
+
) = (
|
|
488
|
+
[None] * self._cap,
|
|
489
|
+
0,
|
|
490
|
+
self._cap - 1,
|
|
491
|
+
0,
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
def fraction_filled(self) -> float:
|
|
495
|
+
"""Find fraction of the storage capacity which is filled.
|
|
496
|
+
|
|
497
|
+
:return: the ratio count/capacity
|
|
498
|
+
|
|
499
|
+
"""
|
|
500
|
+
return self._cnt / self._cap
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def caf[T](*items: T, capacity: int = 2) -> CAF[T]:
|
|
504
|
+
"""Produce a circular array from a variable number of arguments.
|
|
505
|
+
|
|
506
|
+
:param items: initial items for a new circular array
|
|
507
|
+
:param capacity: the minimum storage capacity to set
|
|
508
|
+
:return: new fixed storage capacity circular array
|
|
509
|
+
|
|
510
|
+
"""
|
|
511
|
+
return CAF(items, capacity = capacity)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Copyright 2023-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
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
16
|
+
from typing import Never, TypeVar
|
|
17
|
+
|
|
18
|
+
__all__ = ['CAF', 'caf']
|
|
19
|
+
|
|
20
|
+
I = TypeVar('I')
|
|
21
|
+
T = TypeVar('T')
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CAF[I]():
|
|
25
|
+
|
|
26
|
+
L = TypeVar('L')
|
|
27
|
+
R = TypeVar('R')
|
|
28
|
+
U = TypeVar('U')
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
items: Iterable[I] | None = None,
|
|
33
|
+
capacity: int = 2
|
|
34
|
+
) -> None: ...
|
|
35
|
+
|
|
36
|
+
def __iter__(self) -> Iterator[I]: ...
|
|
37
|
+
|
|
38
|
+
def __reversed__(self) -> Iterator[I]: ...
|
|
39
|
+
|
|
40
|
+
def __repr__(self) -> str: ...
|
|
41
|
+
|
|
42
|
+
def __str__(self) -> str: ...
|
|
43
|
+
|
|
44
|
+
def __bool__(self) -> bool: ...
|
|
45
|
+
|
|
46
|
+
def __len__(self) -> int: ...
|
|
47
|
+
|
|
48
|
+
def __getitem__(self, idx: int) -> I: ...
|
|
49
|
+
|
|
50
|
+
def __setitem__(self, idx: int, val: I) -> None: ...
|
|
51
|
+
|
|
52
|
+
def __delitem__(self, idx: int) -> None: ...
|
|
53
|
+
|
|
54
|
+
def __eq__(self, other: object) -> bool: ...
|
|
55
|
+
|
|
56
|
+
def pushl(self, item: I) -> None: ...
|
|
57
|
+
|
|
58
|
+
def pushr(self, item: I) -> None: ...
|
|
59
|
+
|
|
60
|
+
def popl(self) -> I | Never: ...
|
|
61
|
+
|
|
62
|
+
def popr(self) -> I | Never: ...
|
|
63
|
+
|
|
64
|
+
def popld(self, default: I) -> I: ...
|
|
65
|
+
|
|
66
|
+
def poprd(self, default: I) -> I: ...
|
|
67
|
+
|
|
68
|
+
def poplt(self, maximum: int) -> tuple[I, ...]: ...
|
|
69
|
+
|
|
70
|
+
def poprt(self, maximum: int) -> tuple[I, ...]: ...
|
|
71
|
+
|
|
72
|
+
def rotl(self, n: int = 1) -> None: ...
|
|
73
|
+
|
|
74
|
+
def rotr(self, n: int = 1) -> None: ...
|
|
75
|
+
|
|
76
|
+
def map[U](self, f: Callable[[I], U]) -> CAF[U]: ...
|
|
77
|
+
|
|
78
|
+
def foldl[L](self, f: Callable[[L, I], L], start: L | None = None) -> L | Never: ...
|
|
79
|
+
|
|
80
|
+
def foldr[R](self, f: Callable[[I, R], R], start: R | None = None) -> R | Never: ...
|
|
81
|
+
|
|
82
|
+
def capacity(self) -> int: ...
|
|
83
|
+
|
|
84
|
+
def empty(self) -> None: ...
|
|
85
|
+
|
|
86
|
+
def fraction_filled(self) -> float: ...
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def caf[T](*items: T, capacity: int = 2) -> CAF[T]: ...
|
|
File without changes
|