pythonic-fp-circulararray 5.3.3__py3-none-any.whl → 5.4.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.
@@ -18,38 +18,44 @@ Variable Storage Capacity
18
18
 
19
19
  **Circular array with variable storage capacity.**
20
20
 
21
- - O(1) pops either end
22
- - O(1) amortized pushes either end
21
+ - O(1) pops either end
22
+ - O(1) amortized pushes either end
23
23
  - O(1) indexing, fully supports slicing
24
24
  - auto-resizing more storage capacity when necessary, manually compatible
25
- - iterable, can safely mutate while iterators continue iterating over previous state
25
+ - iterable, safely mutates while iterators iterating over previous state
26
26
  - comparisons compare identity before equality, like builtins
27
27
  - in boolean context, falsy when empty, otherwise truthy
28
- - factory function ``ca`` produces a variable storage capacity circular array from its arguments
28
+ - function ``ca`` produces auto-resizing circular array from arguments
29
29
 
30
30
  """
31
+
31
32
  from collections.abc import Callable, Iterable, Iterator
32
- from typing import cast, overload
33
+ from typing import cast, Final, overload
34
+ from pythonic_fp.gadgets.sentinels.novalue import NoValue
33
35
 
34
36
  __all__ = ['CA', 'ca']
35
37
 
38
+ nada: Final[NoValue] = NoValue()
36
39
 
37
- class CA[I]():
38
40
 
41
+ class CA[I]:
39
42
  __slots__ = '_items', '_cnt', '_cap', '_front', '_rear'
40
43
 
41
- def __init__(
42
- self,
43
- items: Iterable[I] | None = None
44
- ) -> None:
44
+ @overload
45
+ def __init__(self) -> None: ...
46
+ @overload
47
+ def __init__(self, items: Iterable[I]) -> None: ...
48
+
49
+ def __init__(self, items: Iterable[I | NoValue] | NoValue = nada) -> None:
45
50
  """
46
- :param items: Optional iterable to initial populate circular array.
51
+ :param items: "Optional" iterable to initial populate circular array.
47
52
  :raises TypeError: When ``items`` not Iterable.
48
53
  """
49
- if items is None:
50
- self._items: list[I | None] = [None, None]
54
+ if items is nada:
55
+ self._items: list[I | NoValue] = [nada, nada]
51
56
  else:
52
- self._items = [None] + list(items) + [None]
57
+ values: list[I | NoValue] = list(cast(Iterable[I | NoValue], items))
58
+ self._items = [nada] + values + [nada]
53
59
  self._cap = cap = len(self._items)
54
60
  self._cnt = cap - 2
55
61
  if cap == 2:
@@ -62,88 +68,93 @@ class CA[I]():
62
68
  def _double_storage_capacity(self) -> None:
63
69
  if self._front <= self._rear:
64
70
  (
65
- self._items,
66
- self._cap,
71
+ self._items,
72
+ self._cap,
67
73
  ) = (
68
- self._items + [None] * self._cap,
69
- self._cap * 2,
70
- )
74
+ self._items + [nada] * self._cap,
75
+ self._cap * 2,
76
+ )
71
77
  else:
72
78
  (
73
- self._items,
74
- self._front,
75
- self._cap,
79
+ self._items,
80
+ self._front,
81
+ self._cap,
76
82
  ) = (
77
- self._items[: self._front] + [None]*self._cap + self._items[self._front:],
78
- self._front + self._cap,
79
- 2*self._cap,
80
- )
83
+ self._items[: self._front]
84
+ + [nada] * self._cap
85
+ + self._items[self._front :],
86
+ self._front + self._cap,
87
+ 2 * self._cap,
88
+ )
81
89
 
82
90
  def _compact_storage_capacity(self) -> None:
83
91
  match self._cnt:
84
92
  case 0:
85
93
  (
86
- self._cap,
87
- self._front,
88
- self._rear,
89
- self._items,
94
+ self._cap,
95
+ self._front,
96
+ self._rear,
97
+ self._items,
90
98
  ) = (
91
- 2,
92
- 0,
93
- 1,
94
- [None, None],
95
- )
99
+ 2,
100
+ 0,
101
+ 1,
102
+ [nada, nada],
103
+ )
96
104
  case 1:
97
105
  (
106
+ self._cap,
107
+ self._front,
108
+ self._rear,
109
+ self._items,
110
+ ) = (
111
+ 3,
112
+ 1,
113
+ 1,
114
+ [nada, self._items[self._front], nada],
115
+ )
116
+ case _:
117
+ if self._front <= self._rear:
118
+ (
98
119
  self._cap,
99
120
  self._front,
100
121
  self._rear,
101
122
  self._items,
102
- ) = (
103
- 3,
104
- 1,
123
+ ) = (
124
+ self._cnt + 2,
105
125
  1,
106
- [None, self._items[self._front], None],
126
+ self._cnt,
127
+ [nada] + self._items[self._front : self._rear + 1] + [nada],
107
128
  )
108
- case _:
109
- if self._front <= self._rear:
110
- (
111
- self._cap,
112
- self._front,
113
- self._rear,
114
- self._items,
115
- ) = (
116
- self._cnt + 2,
117
- 1,
118
- self._cnt,
119
- [None] + self._items[self._front : self._rear + 1] + [None],
120
- )
121
129
  else:
122
130
  (
123
- self._cap,
124
- self._front,
125
- self._rear,
126
- self._items,
131
+ self._cap,
132
+ self._front,
133
+ self._rear,
134
+ self._items,
127
135
  ) = (
128
- self._cnt + 2,
129
- 1,
130
- self._cnt,
131
- [None] + self._items[self._front :] + self._items[: self._rear + 1] + [None],
132
- )
136
+ self._cnt + 2,
137
+ 1,
138
+ self._cnt,
139
+ [nada]
140
+ + self._items[self._front :]
141
+ + self._items[: self._rear + 1]
142
+ + [nada],
143
+ )
133
144
 
134
145
  def __iter__(self) -> Iterator[I]:
135
146
  if self._cnt > 0:
136
147
  (
137
- capacity,
138
- rear,
139
- position,
140
- current_state,
148
+ capacity,
149
+ rear,
150
+ position,
151
+ current_state,
141
152
  ) = (
142
- self._cap,
143
- self._rear,
144
- self._front,
145
- self._items.copy(),
146
- )
153
+ self._cap,
154
+ self._rear,
155
+ self._front,
156
+ self._items.copy(),
157
+ )
147
158
 
148
159
  while position != rear:
149
160
  yield cast(I, current_state[position])
@@ -153,16 +164,16 @@ class CA[I]():
153
164
  def __reversed__(self) -> Iterator[I]:
154
165
  if self._cnt > 0:
155
166
  (
156
- capacity,
157
- front,
158
- position,
159
- current_state,
167
+ capacity,
168
+ front,
169
+ position,
170
+ current_state,
160
171
  ) = (
161
- self._cap,
162
- self._front,
163
- self._rear,
164
- self._items.copy(),
165
- )
172
+ self._cap,
173
+ self._front,
174
+ self._rear,
175
+ self._items.copy(),
176
+ )
166
177
 
167
178
  while position != front:
168
179
  yield cast(I, current_state[position])
@@ -184,9 +195,9 @@ class CA[I]():
184
195
  @overload
185
196
  def __getitem__(self, idx: int) -> I: ...
186
197
  @overload
187
- def __getitem__(self, idx: slice) -> "CA[I]": ...
198
+ def __getitem__(self, idx: slice) -> 'CA[I]': ...
188
199
 
189
- def __getitem__(self, idx: int | slice) -> I | "CA[I]":
200
+ def __getitem__(self, idx: int | slice) -> I | 'CA[I]':
190
201
  if isinstance(idx, slice):
191
202
  return CA(list(self)[idx])
192
203
 
@@ -218,18 +229,18 @@ class CA[I]():
218
229
  item_list[idx] = vals
219
230
  _ca = CA(item_list)
220
231
  (
221
- self._items,
222
- self._cnt,
223
- self._cap,
224
- self._front,
225
- self._rear,
232
+ self._items,
233
+ self._cnt,
234
+ self._cap,
235
+ self._front,
236
+ self._rear,
226
237
  ) = (
227
- _ca._items,
228
- _ca._cnt,
229
- _ca._cap,
230
- _ca._front,
231
- _ca._rear,
232
- )
238
+ _ca._items,
239
+ _ca._cnt,
240
+ _ca._cap,
241
+ _ca._front,
242
+ _ca._rear,
243
+ )
233
244
  return
234
245
 
235
246
  msg = 'must assign iterable to extended slice'
@@ -259,18 +270,18 @@ class CA[I]():
259
270
  del item_list[idx]
260
271
  _ca = CA(item_list)
261
272
  (
262
- self._items,
263
- self._cnt,
264
- self._cap,
265
- self._front,
266
- self._rear,
273
+ self._items,
274
+ self._cnt,
275
+ self._cap,
276
+ self._front,
277
+ self._rear,
267
278
  ) = (
268
- _ca._items,
269
- _ca._cnt,
270
- _ca._cap,
271
- _ca._front,
272
- _ca._rear,
273
- )
279
+ _ca._items,
280
+ _ca._cnt,
281
+ _ca._cap,
282
+ _ca._front,
283
+ _ca._rear,
284
+ )
274
285
  del _ca
275
286
 
276
287
  def __eq__(self, other: object) -> bool:
@@ -280,28 +291,34 @@ class CA[I]():
280
291
  return False
281
292
 
282
293
  (
283
- front1,
284
- count1,
285
- capacity1,
286
- front2,
287
- count2,
288
- capacity2,
294
+ front1,
295
+ count1,
296
+ capacity1,
297
+ front2,
298
+ count2,
299
+ capacity2,
289
300
  ) = (
290
- self._front,
291
- self._cnt,
292
- self._cap,
293
- other._front,
294
- other._cnt,
295
- other._cap,
296
- )
301
+ self._front,
302
+ self._cnt,
303
+ self._cap,
304
+ other._front,
305
+ other._cnt,
306
+ other._cap,
307
+ )
297
308
 
298
309
  if count1 != count2:
299
310
  return False
300
311
 
301
312
  for nn in range(count1):
302
- if self._items[(front1 + nn) % capacity1] is other._items[(front2 + nn) % capacity2]:
313
+ if (
314
+ self._items[(front1 + nn) % capacity1]
315
+ is other._items[(front2 + nn) % capacity2]
316
+ ):
303
317
  continue
304
- if self._items[(front1 + nn) % capacity1] != other._items[(front2 + nn) % capacity2]:
318
+ if (
319
+ self._items[(front1 + nn) % capacity1]
320
+ != other._items[(front2 + nn) % capacity2]
321
+ ):
305
322
  return False
306
323
  return True
307
324
 
@@ -314,14 +331,14 @@ class CA[I]():
314
331
  if self._cnt == self._cap:
315
332
  self._double_storage_capacity()
316
333
  (
317
- self._front,
318
- self._items[self._front],
319
- self._cnt,
334
+ self._front,
335
+ self._items[self._front],
336
+ self._cnt,
320
337
  ) = (
321
- (self._front - 1) % self._cap,
322
- item,
323
- self._cnt + 1,
324
- )
338
+ (self._front - 1) % self._cap,
339
+ item,
340
+ self._cnt + 1,
341
+ )
325
342
 
326
343
  def pushr(self, *items: I) -> None:
327
344
  """Push ``items`` on from right.
@@ -332,14 +349,14 @@ class CA[I]():
332
349
  if self._cnt == self._cap:
333
350
  self._double_storage_capacity()
334
351
  (
335
- self._rear,
336
- self._items[self._rear],
337
- self._cnt,
352
+ self._rear,
353
+ self._items[self._rear],
354
+ self._cnt,
338
355
  ) = (
339
- (self._rear + 1) % self._cap,
340
- item,
341
- self._cnt + 1,
342
- )
356
+ (self._rear + 1) % self._cap,
357
+ item,
358
+ self._cnt + 1,
359
+ )
343
360
 
344
361
  def popl(self) -> I:
345
362
  """Pop single item off from left side.
@@ -349,30 +366,30 @@ class CA[I]():
349
366
  """
350
367
  if self._cnt > 1:
351
368
  (
352
- d,
353
- self._items[self._front],
354
- self._front,
355
- self._cnt,
369
+ d,
370
+ self._items[self._front],
371
+ self._front,
372
+ self._cnt,
356
373
  ) = (
357
- self._items[self._front],
358
- None,
359
- (self._front + 1) % self._cap,
360
- self._cnt - 1,
361
- )
374
+ self._items[self._front],
375
+ nada,
376
+ (self._front + 1) % self._cap,
377
+ self._cnt - 1,
378
+ )
362
379
  elif self._cnt == 1:
363
380
  (
364
- d,
365
- self._items[self._front],
366
- self._cnt,
367
- self._front,
368
- self._rear,
381
+ d,
382
+ self._items[self._front],
383
+ self._cnt,
384
+ self._front,
385
+ self._rear,
369
386
  ) = (
370
- self._items[self._front],
371
- None,
372
- 0,
373
- 0,
374
- self._cap - 1,
375
- )
387
+ self._items[self._front],
388
+ nada,
389
+ 0,
390
+ 0,
391
+ self._cap - 1,
392
+ )
376
393
  else:
377
394
  msg = 'Method popl called on an empty CA'
378
395
  raise ValueError(msg)
@@ -386,30 +403,30 @@ class CA[I]():
386
403
  """
387
404
  if self._cnt > 1:
388
405
  (
389
- d,
390
- self._items[self._rear],
391
- self._rear,
392
- self._cnt,
406
+ d,
407
+ self._items[self._rear],
408
+ self._rear,
409
+ self._cnt,
393
410
  ) = (
394
- self._items[self._rear],
395
- None,
396
- (self._rear - 1) % self._cap,
397
- self._cnt - 1,
398
- )
411
+ self._items[self._rear],
412
+ nada,
413
+ (self._rear - 1) % self._cap,
414
+ self._cnt - 1,
415
+ )
399
416
  elif self._cnt == 1:
400
417
  (
401
- d,
402
- self._items[self._front],
403
- self._cnt,
404
- self._front,
405
- self._rear,
418
+ d,
419
+ self._items[self._front],
420
+ self._cnt,
421
+ self._front,
422
+ self._rear,
406
423
  ) = (
407
- self._items[self._front],
408
- None,
409
- 0,
410
- 0,
411
- self._cap - 1,
412
- )
424
+ self._items[self._front],
425
+ nada,
426
+ 0,
427
+ 0,
428
+ self._cap - 1,
429
+ )
413
430
  else:
414
431
  msg = 'Method popr called on an empty CA'
415
432
  raise ValueError(msg)
@@ -493,7 +510,7 @@ class CA[I]():
493
510
  for _ in range(n, 0, -1):
494
511
  self.pushl(self.popr())
495
512
 
496
- def map[U](self, f: Callable[[I], U]) -> "CA[U]":
513
+ def map[U](self, f: Callable[[I], U]) -> 'CA[U]':
497
514
  """Apply function ``f`` over the circular array's contents.
498
515
 
499
516
  :param f: Callable from type ``I`` to type ``U``.
@@ -501,52 +518,62 @@ class CA[I]():
501
518
  """
502
519
  return CA(map(f, self))
503
520
 
504
- def foldl[L](self, f: Callable[[L, I], L], start: L | None = None) -> L:
521
+ @overload
522
+ def foldl[L](self, f: Callable[[I, I], I]) -> I: ...
523
+ @overload
524
+ def foldl[L](self, f: Callable[[L, I], L], start: L) -> L: ...
525
+
526
+ def foldl[L](self, f: Callable[[L, I], L], start: L | NoValue = NoValue()) -> L:
505
527
  """Fold left with a function and optional starting item.
506
528
 
507
529
  :param f: Folding function, first argument to ``f`` is for the accumulator.
508
- :param start: Optional starting item.
530
+ :param start: "Optional" starting item.
509
531
  :returns: Reduced value produced by the left fold.
510
532
  :raises ValueError: When circular array empty and no starting item given.
511
533
  """
512
534
  if self._cnt == 0:
513
- if start is None:
535
+ if start is nada:
514
536
  msg = 'Method foldl called on an empty CA without a start item.'
515
537
  raise ValueError(msg)
516
- return start
538
+ return cast(L, start)
517
539
 
518
- if start is None:
540
+ if start is nada:
519
541
  acc = cast(L, self[0]) # in this case D = L
520
542
  for idx in range(1, self._cnt):
521
543
  acc = f(acc, self[idx])
522
544
  return acc
523
545
 
524
- acc = start
546
+ acc = cast(L, start)
525
547
  for d in self:
526
548
  acc = f(acc, d)
527
549
  return acc
528
550
 
529
- def foldr[R](self, f: Callable[[I, R], R], start: R | None = None) -> R:
551
+ @overload
552
+ def foldr[R](self, f: Callable[[I, I], I]) -> I: ...
553
+ @overload
554
+ def foldr[R](self, f: Callable[[I, R], R], start: R) -> R: ...
555
+
556
+ def foldr[R](self, f: Callable[[I, R], R], start: R | NoValue = nada) -> R:
530
557
  """Fold right with a function and an optional starting item.
531
558
 
532
559
  :param f: Folding function, second argument to ``f`` is for the accumulator.
533
- :param start: Optional starting item.
560
+ :param start: "Optional" starting item.
534
561
  :returns: Reduced value produced by the right fold.
535
562
  :raises ValueError: When circular array empty and no starting item given.
536
563
  """
537
564
  if self._cnt == 0:
538
- if start is None:
565
+ if start is nada:
539
566
  msg = 'Method foldr called on empty CA without initial value.'
540
567
  raise ValueError(msg)
541
- return start
568
+ return cast(R, start)
542
569
 
543
- if start is None:
570
+ if start is nada:
544
571
  acc = cast(R, self[-1]) # in this case D = R
545
572
  for idx in range(self._cnt - 2, -1, -1):
546
573
  acc = f(self[idx], acc)
547
574
  return acc
548
575
 
549
- acc = start
576
+ acc = cast(R, start)
550
577
  for d in reversed(self):
551
578
  acc = f(d, acc)
552
579
  return acc
@@ -561,16 +588,16 @@ class CA[I]():
561
588
  def empty(self) -> None:
562
589
  """Empty the circular array, keep current storage capacity."""
563
590
  (
564
- self._items,
565
- self._front,
566
- self._rear,
567
- self._cnt,
591
+ self._items,
592
+ self._front,
593
+ self._rear,
594
+ self._cnt,
568
595
  ) = (
569
- [None] * self._cap,
570
- 0,
571
- self._cap - 1,
572
- 0,
573
- )
596
+ [nada] * self._cap,
597
+ 0,
598
+ self._cap - 1,
599
+ 0,
600
+ )
574
601
 
575
602
  def fraction_filled(self) -> float:
576
603
  """Find fraction of the storage capacity which is filled.
@@ -589,12 +616,12 @@ class CA[I]():
589
616
  self._compact_storage_capacity()
590
617
  if (min_cap := minimum_capacity) > self._cap:
591
618
  (
592
- self._cap,
593
- self._items,
619
+ self._cap,
620
+ self._items,
594
621
  ) = (
595
- min_cap,
596
- self._items + [None] * (min_cap - self._cap),
597
- )
622
+ min_cap,
623
+ self._items + [nada] * (min_cap - self._cap),
624
+ )
598
625
  if self._cnt == 0:
599
626
  self._front, self._rear = 0, self._cap - 1
600
627
 
@@ -4,7 +4,10 @@ from typing import overload
4
4
  __all__ = ['CA', 'ca']
5
5
 
6
6
  class CA[I]:
7
- def __init__(self, items: Iterable[I] | None = None) -> None: ...
7
+ @overload
8
+ def __init__(self) -> None: ...
9
+ @overload
10
+ def __init__(self, items: Iterable[I]) -> None: ...
8
11
  def __iter__(self) -> Iterator[I]: ...
9
12
  def __reversed__(self) -> Iterator[I]: ...
10
13
  def __bool__(self) -> bool: ...
@@ -33,8 +36,14 @@ class CA[I]:
33
36
  def rotl(self, n: int = 1) -> None: ...
34
37
  def rotr(self, n: int = 1) -> None: ...
35
38
  def map[U](self, f: Callable[[I], U]) -> CA[U]: ...
36
- def foldl[L](self, f: Callable[[L, I], L], start: L | None = None) -> L: ...
37
- def foldr[R](self, f: Callable[[I, R], R], start: R | None = None) -> R: ...
39
+ @overload
40
+ def foldl[L](self, f: Callable[[I, I], I]) -> I: ...
41
+ @overload
42
+ def foldl[L](self, f: Callable[[L, I], L], start: L) -> L: ...
43
+ @overload
44
+ def foldr[R](self, f: Callable[[I, I], I]) -> I: ...
45
+ @overload
46
+ def foldr[R](self, f: Callable[[I, R], R], start: R) -> R: ...
38
47
  def capacity(self) -> int: ...
39
48
  def empty(self) -> None: ...
40
49
  def fraction_filled(self) -> float: ...
@@ -18,44 +18,53 @@ Fixed Storage Capacity
18
18
 
19
19
  **Circular array with fixed storage capacity.**
20
20
 
21
- - O(1) pops and pushes either end
21
+ - O(1) pops and pushes either end
22
22
  - O(1) indexing, does not support slicing
23
23
  - fixed total storage capacity
24
- - iterable, can safely mutate while iterators continue iterating over previous state
24
+ - iterable, safely mutates while iterators iterating over previous state
25
25
  - comparisons compare identity before equality, like builtins
26
26
  - in boolean context, falsy when either empty or full, otherwise truthy
27
- - factory function ``caf`` produces a fixed storage capacity circular array from its arguments
27
+ - function ``caf`` produces fixed capacity circular array from arguments
28
28
 
29
29
  """
30
+
30
31
  from collections.abc import Callable, Iterable, Iterator
31
- from typing import cast, Final
32
+ from typing import cast, Final, overload
33
+ from pythonic_fp.gadgets.sentinels.novalue import NoValue
32
34
 
33
35
  __all__ = ['CAF', 'caf']
34
36
 
37
+ nada: Final[NoValue] = NoValue()
35
38
 
36
- class CAF[I]():
37
39
 
40
+ class CAF[I]:
38
41
  __slots__ = '_items', '_cnt', '_cap', '_front', '_rear'
39
42
 
43
+ @overload
44
+ def __init__(self) -> None: ...
45
+ @overload
46
+ def __init__(self, items: Iterable[I]) -> None: ...
47
+ @overload
48
+ def __init__(self, items: Iterable[I], capacity: int) -> None: ...
49
+
40
50
  def __init__(
41
- self,
42
- items: Iterable[I] | None = None,
43
- capacity: int = 2
44
- ) -> None:
51
+ self, items: Iterable[I | NoValue] | NoValue = nada, capacity: int = 2
52
+ ) -> None:
45
53
  """
46
- :param items: Optional iterable to initial populate circular array.
47
- :param capacity: Fixed storage capacity of circular array.
54
+ :param items: "Optional" iterable to initial populate circular array.
55
+ :param capacity: Minimum fixed storage capacity of circular array.
48
56
  :raises TypeError: When ``items`` not Iterable,
49
57
  """
50
58
  capacity = max(2, capacity)
51
- if items is None:
52
- self._items: list[I | None] = [None]*capacity
59
+ if items is nada:
60
+ self._items: list[I | NoValue] = [nada] * capacity
53
61
  count = 0
54
62
  else:
55
- dlist: list[I | None] = list(items)
63
+ values: list[I | NoValue] = list(cast(Iterable[I | NoValue], items))
64
+ dlist: list[I | NoValue] = list(values)
56
65
  count = len(dlist)
57
66
  capacity = max(count, capacity)
58
- self._items = dlist + [None]*(capacity - count)
67
+ self._items = dlist + [nada] * (capacity - count)
59
68
  self._cap: Final[int] = capacity
60
69
  self._cnt = count
61
70
  if count == 0:
@@ -68,16 +77,16 @@ class CAF[I]():
68
77
  def __iter__(self) -> Iterator[I]:
69
78
  if self._cnt > 0:
70
79
  (
71
- capacity,
72
- rear,
73
- position,
74
- current_state,
80
+ capacity,
81
+ rear,
82
+ position,
83
+ current_state,
75
84
  ) = (
76
- self._cap,
77
- self._rear,
78
- self._front,
79
- self._items.copy(),
80
- )
85
+ self._cap,
86
+ self._rear,
87
+ self._front,
88
+ self._items.copy(),
89
+ )
81
90
 
82
91
  while position != rear:
83
92
  yield cast(I, current_state[position])
@@ -87,16 +96,16 @@ class CAF[I]():
87
96
  def __reversed__(self) -> Iterator[I]:
88
97
  if self._cnt > 0:
89
98
  (
90
- capacity,
91
- front,
92
- position,
93
- current_state,
99
+ capacity,
100
+ front,
101
+ position,
102
+ current_state,
94
103
  ) = (
95
- self._cap,
96
- self._front,
97
- self._rear,
98
- self._items.copy(),
99
- )
104
+ self._cap,
105
+ self._front,
106
+ self._rear,
107
+ self._items.copy(),
108
+ )
100
109
 
101
110
  while position != front:
102
111
  yield cast(I, current_state[position])
@@ -152,16 +161,16 @@ class CAF[I]():
152
161
  del item_list[idx]
153
162
  _ca = CAF(item_list, self._cap)
154
163
  (
155
- self._items,
156
- self._cnt,
157
- self._front,
158
- self._rear,
164
+ self._items,
165
+ self._cnt,
166
+ self._front,
167
+ self._rear,
159
168
  ) = (
160
- _ca._items,
161
- _ca._cnt,
162
- _ca._front,
163
- _ca._rear,
164
- )
169
+ _ca._items,
170
+ _ca._cnt,
171
+ _ca._front,
172
+ _ca._rear,
173
+ )
165
174
  del _ca
166
175
 
167
176
  def __eq__(self, other: object) -> bool:
@@ -171,28 +180,34 @@ class CAF[I]():
171
180
  return False
172
181
 
173
182
  (
174
- front1,
175
- count1,
176
- capacity1,
177
- front2,
178
- count2,
179
- capacity2,
183
+ front1,
184
+ count1,
185
+ capacity1,
186
+ front2,
187
+ count2,
188
+ capacity2,
180
189
  ) = (
181
- self._front,
182
- self._cnt,
183
- self._cap,
184
- other._front,
185
- other._cnt,
186
- other._cap,
187
- )
190
+ self._front,
191
+ self._cnt,
192
+ self._cap,
193
+ other._front,
194
+ other._cnt,
195
+ other._cap,
196
+ )
188
197
 
189
198
  if count1 != count2:
190
199
  return False
191
200
 
192
201
  for nn in range(count1):
193
- if self._items[(front1 + nn) % capacity1] is other._items[(front2 + nn) % capacity2]:
202
+ if (
203
+ self._items[(front1 + nn) % capacity1]
204
+ is other._items[(front2 + nn) % capacity2]
205
+ ):
194
206
  continue
195
- if self._items[(front1 + nn) % capacity1] != other._items[(front2 + nn) % capacity2]:
207
+ if (
208
+ self._items[(front1 + nn) % capacity1]
209
+ != other._items[(front2 + nn) % capacity2]
210
+ ):
196
211
  return False
197
212
  return True
198
213
 
@@ -207,14 +222,14 @@ class CAF[I]():
207
222
  raise ValueError(msg)
208
223
 
209
224
  (
210
- self._front,
211
- self._items[self._front],
212
- self._cnt,
225
+ self._front,
226
+ self._items[self._front],
227
+ self._cnt,
213
228
  ) = (
214
- (self._front - 1) % self._cap,
215
- item,
216
- self._cnt + 1,
217
- )
229
+ (self._front - 1) % self._cap,
230
+ item,
231
+ self._cnt + 1,
232
+ )
218
233
 
219
234
  def pushr(self, item: I) -> None:
220
235
  """Push ``item`` on from Right.
@@ -227,14 +242,14 @@ class CAF[I]():
227
242
  raise ValueError(msg)
228
243
 
229
244
  (
230
- self._rear,
231
- self._items[self._rear],
232
- self._cnt,
245
+ self._rear,
246
+ self._items[self._rear],
247
+ self._cnt,
233
248
  ) = (
234
- (self._rear + 1) % self._cap,
235
- item,
236
- self._cnt + 1,
237
- )
249
+ (self._rear + 1) % self._cap,
250
+ item,
251
+ self._cnt + 1,
252
+ )
238
253
 
239
254
  def popl(self) -> I:
240
255
  """Pop single item off from left side.
@@ -244,30 +259,30 @@ class CAF[I]():
244
259
  """
245
260
  if self._cnt > 1:
246
261
  (
247
- d,
248
- self._items[self._front],
249
- self._front,
250
- self._cnt,
262
+ d,
263
+ self._items[self._front],
264
+ self._front,
265
+ self._cnt,
251
266
  ) = (
252
- self._items[self._front],
253
- None,
254
- (self._front + 1) % self._cap,
255
- self._cnt - 1,
256
- )
267
+ self._items[self._front],
268
+ nada,
269
+ (self._front + 1) % self._cap,
270
+ self._cnt - 1,
271
+ )
257
272
  elif self._cnt == 1:
258
273
  (
259
- d,
260
- self._items[self._front],
261
- self._cnt,
262
- self._front,
263
- self._rear,
274
+ d,
275
+ self._items[self._front],
276
+ self._cnt,
277
+ self._front,
278
+ self._rear,
264
279
  ) = (
265
- self._items[self._front],
266
- None,
267
- 0,
268
- 0,
269
- self._cap - 1,
270
- )
280
+ self._items[self._front],
281
+ nada,
282
+ 0,
283
+ 0,
284
+ self._cap - 1,
285
+ )
271
286
  else:
272
287
  msg = 'Method popl called on an empty CAF'
273
288
  raise ValueError(msg)
@@ -281,30 +296,30 @@ class CAF[I]():
281
296
  """
282
297
  if self._cnt > 1:
283
298
  (
284
- d,
285
- self._items[self._rear],
286
- self._rear,
287
- self._cnt,
299
+ d,
300
+ self._items[self._rear],
301
+ self._rear,
302
+ self._cnt,
288
303
  ) = (
289
- self._items[self._rear],
290
- None,
291
- (self._rear - 1) % self._cap,
292
- self._cnt - 1,
293
- )
304
+ self._items[self._rear],
305
+ nada,
306
+ (self._rear - 1) % self._cap,
307
+ self._cnt - 1,
308
+ )
294
309
  elif self._cnt == 1:
295
310
  (
296
- d,
297
- self._items[self._front],
298
- self._cnt,
299
- self._front,
300
- self._rear,
311
+ d,
312
+ self._items[self._front],
313
+ self._cnt,
314
+ self._front,
315
+ self._rear,
301
316
  ) = (
302
- self._items[self._front],
303
- None,
304
- 0,
305
- 0,
306
- self._cap - 1,
307
- )
317
+ self._items[self._front],
318
+ nada,
319
+ 0,
320
+ 0,
321
+ self._cap - 1,
322
+ )
308
323
  else:
309
324
  msg = 'Method popr called on an empty CAF'
310
325
  raise ValueError(msg)
@@ -388,7 +403,7 @@ class CAF[I]():
388
403
  for _ in range(n, 0, -1):
389
404
  self.pushl(self.popr())
390
405
 
391
- def map[U](self, f: Callable[[I], U]) -> "CAF[U]":
406
+ def map[U](self, f: Callable[[I], U]) -> 'CAF[U]':
392
407
  """Apply function ``f`` over the circular array's contents,
393
408
 
394
409
  :param f: Callable from type ``I`` to type ``U``.
@@ -396,7 +411,12 @@ class CAF[I]():
396
411
  """
397
412
  return CAF(map(f, self), self._cap)
398
413
 
399
- def foldl[L](self, f: Callable[[L, I], L], start: L | None = None) -> L:
414
+ @overload
415
+ def foldl[L](self, f: Callable[[I, I], I]) -> I: ...
416
+ @overload
417
+ def foldl[L](self, f: Callable[[L, I], L], start: L) -> L: ...
418
+
419
+ def foldl[L](self, f: Callable[[L, I], L], start: L | NoValue = nada) -> L:
400
420
  """Fold left with a function and optional stating item.
401
421
 
402
422
  :param f: Folding function, first argument to ``f`` is for the accumulator.
@@ -405,23 +425,28 @@ class CAF[I]():
405
425
  :raises ValueError: When circular array empty and no starting item given.
406
426
  """
407
427
  if self._cnt == 0:
408
- if start is None:
428
+ if start is nada:
409
429
  msg = 'Method foldl called on an empty CAF without a start item.'
410
430
  raise ValueError(msg)
411
- return start
431
+ return cast(L, start)
412
432
 
413
- if start is None:
433
+ if start is nada:
414
434
  acc = cast(L, self[0]) # in this case D = L
415
435
  for idx in range(1, self._cnt):
416
436
  acc = f(acc, self[idx])
417
437
  return acc
418
438
 
419
- acc = start
439
+ acc = cast(L, start)
420
440
  for d in self:
421
441
  acc = f(acc, d)
422
442
  return acc
423
443
 
424
- def foldr[R](self, f: Callable[[I, R], R], start: R | None = None) -> R:
444
+ @overload
445
+ def foldr[R](self, f: Callable[[I, I], I]) -> I: ...
446
+ @overload
447
+ def foldr[R](self, f: Callable[[I, R], R], start: R) -> R: ...
448
+
449
+ def foldr[R](self, f: Callable[[I, R], R], start: R | NoValue = nada) -> R:
425
450
  """Fold right with a function and an optional starting item.
426
451
 
427
452
  :param f: Folding function, second argument to ``f`` is for the accumulator.
@@ -430,18 +455,18 @@ class CAF[I]():
430
455
  :raises ValueError: When circular array empty and no starting item given.
431
456
  """
432
457
  if self._cnt == 0:
433
- if start is None:
458
+ if start is nada:
434
459
  msg = 'Method foldr called on empty CAF without initial value.'
435
460
  raise ValueError(msg)
436
- return start
461
+ return cast(R, start)
437
462
 
438
- if start is None:
463
+ if start is nada:
439
464
  acc = cast(R, self[-1]) # in this case D = R
440
465
  for idx in range(self._cnt - 2, -1, -1):
441
466
  acc = f(self[idx], acc)
442
467
  return acc
443
468
 
444
- acc = start
469
+ acc = cast(R, start)
445
470
  for d in reversed(self):
446
471
  acc = f(d, acc)
447
472
  return acc
@@ -456,16 +481,16 @@ class CAF[I]():
456
481
  def empty(self) -> None:
457
482
  """Empty the circular array."""
458
483
  (
459
- self._items,
460
- self._front,
461
- self._rear,
462
- self._cnt,
484
+ self._items,
485
+ self._front,
486
+ self._rear,
487
+ self._cnt,
463
488
  ) = (
464
- [None] * self._cap,
465
- 0,
466
- self._cap - 1,
467
- 0,
468
- )
489
+ [nada] * self._cap,
490
+ 0,
491
+ self._cap - 1,
492
+ 0,
493
+ )
469
494
 
470
495
  def fraction_filled(self) -> float:
471
496
  """Find fraction of the storage capacity which is filled.
@@ -482,4 +507,4 @@ def caf[T](*items: T, capacity: int = 2) -> CAF[T]:
482
507
  :param capacity: The minimum storage capacity to set.
483
508
  :returns: New fixed storage capacity circular array.
484
509
  """
485
- return CAF(items, capacity = capacity)
510
+ return CAF(items, capacity=capacity)
@@ -1,9 +1,15 @@
1
1
  from collections.abc import Callable, Iterable, Iterator
2
+ from typing import overload
2
3
 
3
4
  __all__ = ['CAF', 'caf']
4
5
 
5
6
  class CAF[I]:
6
- def __init__(self, items: Iterable[I] | None = None, capacity: int = 2) -> None: ...
7
+ @overload
8
+ def __init__(self) -> None: ...
9
+ @overload
10
+ def __init__(self, items: Iterable[I]) -> None: ...
11
+ @overload
12
+ def __init__(self, items: Iterable[I], capacity: int) -> None: ...
7
13
  def __iter__(self) -> Iterator[I]: ...
8
14
  def __reversed__(self) -> Iterator[I]: ...
9
15
  def __bool__(self) -> bool: ...
@@ -23,8 +29,14 @@ class CAF[I]:
23
29
  def rotl(self, n: int = 1) -> None: ...
24
30
  def rotr(self, n: int = 1) -> None: ...
25
31
  def map[U](self, f: Callable[[I], U]) -> CAF[U]: ...
26
- def foldl[L](self, f: Callable[[L, I], L], start: L | None = None) -> L: ...
27
- def foldr[R](self, f: Callable[[I, R], R], start: R | None = None) -> R: ...
32
+ @overload
33
+ def foldl[L](self, f: Callable[[I, I], I]) -> I: ...
34
+ @overload
35
+ def foldl[L](self, f: Callable[[L, I], L], start: L) -> L: ...
36
+ @overload
37
+ def foldr[R](self, f: Callable[[I, I], I]) -> I: ...
38
+ @overload
39
+ def foldr[R](self, f: Callable[[I, R], R], start: R) -> R: ...
28
40
  def capacity(self) -> int: ...
29
41
  def empty(self) -> None: ...
30
42
  def fraction_filled(self) -> float: ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonic-fp-circulararray
3
- Version: 5.3.3
3
+ Version: 5.4.0
4
4
  Summary: Circular Array
5
5
  Keywords: auto resizing,circular array,circulararray,dequeue,indexable,pop,push
6
6
  Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
@@ -14,6 +14,7 @@ Classifier: Operating System :: OS Independent
14
14
  Classifier: Programming Language :: Python :: 3.13
15
15
  Classifier: Typing :: Typed
16
16
  License-File: LICENSE
17
+ Requires-Dist: pythonic-fp-gadgets>=3.1.0
17
18
  Requires-Dist: pytest>=8.4.1 ; extra == "test"
18
19
  Project-URL: Changelog, https://github.com/grscheller/pythonic-fp-circulararray/blob/main/CHANGELOG.rst
19
20
  Project-URL: Documentation, https://grscheller.github.io/pythonic-fp/circulararray/development/build/html/index.html
@@ -30,17 +31,31 @@ PyPI project
30
31
 
31
32
  Python module implementing stateful circular array data structures.
32
33
 
33
- - O(1) pops either end
34
- - O(1) amortized pushes either end
35
- - O(1) indexing, fully supports slicing
36
- - Auto-resizing larger when necessary, manually compatible
37
- - iterable, can safely mutate while iterators continue iterating over previous state
38
- - comparisons compare identity before equality, like builtins
39
- - in boolean context returns true when not empty, false when empty
40
-
41
- This PyPI project is part of of the grscheller
42
- `pythonic-fp namespace projects
43
- <https://github.com/grscheller/pythonic-fp/blob/main/README.md>`_
34
+ - variable storage capacity circular array
35
+
36
+ - O(1) pops either end
37
+ - O(1) amortized pushes either end
38
+ - O(1) indexing, fully supports slicing
39
+ - auto-resizing more storage capacity when necessary, manually compatible
40
+ - iterable, safely mutates while iterators iterating over previous state
41
+ - comparisons compare identity before equality, like builtins
42
+ - in boolean context, falsy when empty, otherwise truthy
43
+ - function ``ca`` produces auto-resizing circular array from arguments
44
+
45
+ - fixed storage capacity circular array
46
+
47
+ - O(1) pops and pushes either end
48
+ - O(1) indexing, does not support slicing
49
+ - fixed total storage capacity
50
+ - iterable, safely mutates while iterators iterating over previous state
51
+ - comparisons compare identity before equality, like builtins
52
+ - in boolean context, falsy when either empty or full, otherwise truthy
53
+ - function ``caf`` produces fixed capacity circular array from arguments
54
+
55
+ Part of the
56
+ `pythonic-fp
57
+ <https://grscheller.github.io/pythonic-fp/homepage/build/html/index.html>`_
58
+ PyPI projects.
44
59
 
45
60
  Documentation
46
61
  -------------
@@ -0,0 +1,11 @@
1
+ pythonic_fp/circulararray/__init__.py,sha256=uR0KASmw1Zraz9l3VPbqPm9WUKzWk-0_tUGT0opabzQ,1515
2
+ pythonic_fp/circulararray/__init__.pyi,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ pythonic_fp/circulararray/auto.py,sha256=mM5kAZnSo4etgA0uSp05VUMGpIOeKXIoCCQ1C3pIETo,19457
4
+ pythonic_fp/circulararray/auto.pyi,sha256=DegIbRFv66Ws4N1J1dExQ6cQJOTZw-XXUEJHi1OgIBk,1861
5
+ pythonic_fp/circulararray/fixed.py,sha256=VhCWBcE0C8toYqGyd9aKJ1bSJu8sHA46AQGJ7mGxYBE,15700
6
+ pythonic_fp/circulararray/fixed.pyi,sha256=Uk0QnXn3_q_6urXeRFEMxsL0gS-3Xa4UsyDRVXk8YoY,1648
7
+ pythonic_fp/circulararray/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ pythonic_fp_circulararray-5.4.0.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
9
+ pythonic_fp_circulararray-5.4.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
10
+ pythonic_fp_circulararray-5.4.0.dist-info/METADATA,sha256=x6ZQX_ItPF3ndJZFS15JOOqKPK01yM6YEf0Y3e9PsK8,2761
11
+ pythonic_fp_circulararray-5.4.0.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- pythonic_fp/circulararray/__init__.py,sha256=uR0KASmw1Zraz9l3VPbqPm9WUKzWk-0_tUGT0opabzQ,1515
2
- pythonic_fp/circulararray/__init__.pyi,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- pythonic_fp/circulararray/auto.py,sha256=_rZhOsC3PGs97j15qM4vXpkpRDTfVkxEGwYyBi9uAQU,19334
4
- pythonic_fp/circulararray/auto.pyi,sha256=yfQ2kzIiXFb2Bpg3cMwHwv2oSlyOUMTgmdPZM_-oU_8,1669
5
- pythonic_fp/circulararray/fixed.py,sha256=nzwPnaVOh-KKv503_c--f_m0SkOaUI6mSe1Bo5WACCA,15316
6
- pythonic_fp/circulararray/fixed.pyi,sha256=y_7eMvH18B1lpLPp2P-VkN-xcQVKJn6cOisp9LSBBRk,1362
7
- pythonic_fp/circulararray/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- pythonic_fp_circulararray-5.3.3.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
9
- pythonic_fp_circulararray-5.3.3.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
10
- pythonic_fp_circulararray-5.3.3.dist-info/METADATA,sha256=rdgGmosD6wF1Cfye7QBuZJXGnjs3uDgPzmYYvVpUx-M,2180
11
- pythonic_fp_circulararray-5.3.3.dist-info/RECORD,,