pythonic-fp-circulararray 5.3.3__py3-none-any.whl → 6.0.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,39 +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
+ def __init__(self, *items: Iterable[I]) -> None:
45
45
  """
46
- :param items: Optional iterable to initial populate circular array.
47
- :raises TypeError: When ``items`` not Iterable.
46
+ :param items: "Optionally" takes a single iterable to populate circular array.
47
+ :raises TypeError: When ``items[0]`` not iterable.
48
+ :raises ValueError: If more than 1 iterable is given.
48
49
  """
49
- if items is None:
50
- self._items: list[I | None] = [None, None]
50
+ if (size := len(items)) > 1:
51
+ msg = f'CA expects at most 1 argument, got {size}'
52
+ raise ValueError(msg)
53
+ if size:
54
+ values: list[I | NoValue] = list(cast(Iterable[I | NoValue], items[0]))
55
+ self._items = [nada] + values + [nada]
51
56
  else:
52
- self._items = [None] + list(items) + [None]
53
- self._cap = cap = len(self._items)
57
+ self._items = [nada, nada]
58
+ self._cap = (cap := len(self._items))
54
59
  self._cnt = cap - 2
55
60
  if cap == 2:
56
61
  self._front = 0
@@ -62,88 +67,93 @@ class CA[I]():
62
67
  def _double_storage_capacity(self) -> None:
63
68
  if self._front <= self._rear:
64
69
  (
65
- self._items,
66
- self._cap,
70
+ self._items,
71
+ self._cap,
67
72
  ) = (
68
- self._items + [None] * self._cap,
69
- self._cap * 2,
70
- )
73
+ self._items + [nada] * self._cap,
74
+ self._cap * 2,
75
+ )
71
76
  else:
72
77
  (
73
- self._items,
74
- self._front,
75
- self._cap,
78
+ self._items,
79
+ self._front,
80
+ self._cap,
76
81
  ) = (
77
- self._items[: self._front] + [None]*self._cap + self._items[self._front:],
78
- self._front + self._cap,
79
- 2*self._cap,
80
- )
82
+ self._items[: self._front]
83
+ + [nada] * self._cap
84
+ + self._items[self._front :],
85
+ self._front + self._cap,
86
+ 2 * self._cap,
87
+ )
81
88
 
82
89
  def _compact_storage_capacity(self) -> None:
83
90
  match self._cnt:
84
91
  case 0:
85
92
  (
86
- self._cap,
87
- self._front,
88
- self._rear,
89
- self._items,
93
+ self._cap,
94
+ self._front,
95
+ self._rear,
96
+ self._items,
90
97
  ) = (
91
- 2,
92
- 0,
93
- 1,
94
- [None, None],
95
- )
98
+ 2,
99
+ 0,
100
+ 1,
101
+ [nada, nada],
102
+ )
96
103
  case 1:
97
104
  (
105
+ self._cap,
106
+ self._front,
107
+ self._rear,
108
+ self._items,
109
+ ) = (
110
+ 3,
111
+ 1,
112
+ 1,
113
+ [nada, self._items[self._front], nada],
114
+ )
115
+ case _:
116
+ if self._front <= self._rear:
117
+ (
98
118
  self._cap,
99
119
  self._front,
100
120
  self._rear,
101
121
  self._items,
102
- ) = (
103
- 3,
104
- 1,
122
+ ) = (
123
+ self._cnt + 2,
105
124
  1,
106
- [None, self._items[self._front], None],
125
+ self._cnt,
126
+ [nada] + self._items[self._front : self._rear + 1] + [nada],
107
127
  )
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
128
  else:
122
129
  (
123
- self._cap,
124
- self._front,
125
- self._rear,
126
- self._items,
130
+ self._cap,
131
+ self._front,
132
+ self._rear,
133
+ self._items,
127
134
  ) = (
128
- self._cnt + 2,
129
- 1,
130
- self._cnt,
131
- [None] + self._items[self._front :] + self._items[: self._rear + 1] + [None],
132
- )
135
+ self._cnt + 2,
136
+ 1,
137
+ self._cnt,
138
+ [nada]
139
+ + self._items[self._front :]
140
+ + self._items[: self._rear + 1]
141
+ + [nada],
142
+ )
133
143
 
134
144
  def __iter__(self) -> Iterator[I]:
135
145
  if self._cnt > 0:
136
146
  (
137
- capacity,
138
- rear,
139
- position,
140
- current_state,
147
+ capacity,
148
+ rear,
149
+ position,
150
+ current_state,
141
151
  ) = (
142
- self._cap,
143
- self._rear,
144
- self._front,
145
- self._items.copy(),
146
- )
152
+ self._cap,
153
+ self._rear,
154
+ self._front,
155
+ self._items.copy(),
156
+ )
147
157
 
148
158
  while position != rear:
149
159
  yield cast(I, current_state[position])
@@ -153,16 +163,16 @@ class CA[I]():
153
163
  def __reversed__(self) -> Iterator[I]:
154
164
  if self._cnt > 0:
155
165
  (
156
- capacity,
157
- front,
158
- position,
159
- current_state,
166
+ capacity,
167
+ front,
168
+ position,
169
+ current_state,
160
170
  ) = (
161
- self._cap,
162
- self._front,
163
- self._rear,
164
- self._items.copy(),
165
- )
171
+ self._cap,
172
+ self._front,
173
+ self._rear,
174
+ self._items.copy(),
175
+ )
166
176
 
167
177
  while position != front:
168
178
  yield cast(I, current_state[position])
@@ -184,9 +194,9 @@ class CA[I]():
184
194
  @overload
185
195
  def __getitem__(self, idx: int) -> I: ...
186
196
  @overload
187
- def __getitem__(self, idx: slice) -> "CA[I]": ...
197
+ def __getitem__(self, idx: slice) -> 'CA[I]': ...
188
198
 
189
- def __getitem__(self, idx: int | slice) -> I | "CA[I]":
199
+ def __getitem__(self, idx: int | slice) -> I | 'CA[I]':
190
200
  if isinstance(idx, slice):
191
201
  return CA(list(self)[idx])
192
202
 
@@ -218,18 +228,18 @@ class CA[I]():
218
228
  item_list[idx] = vals
219
229
  _ca = CA(item_list)
220
230
  (
221
- self._items,
222
- self._cnt,
223
- self._cap,
224
- self._front,
225
- self._rear,
231
+ self._items,
232
+ self._cnt,
233
+ self._cap,
234
+ self._front,
235
+ self._rear,
226
236
  ) = (
227
- _ca._items,
228
- _ca._cnt,
229
- _ca._cap,
230
- _ca._front,
231
- _ca._rear,
232
- )
237
+ _ca._items,
238
+ _ca._cnt,
239
+ _ca._cap,
240
+ _ca._front,
241
+ _ca._rear,
242
+ )
233
243
  return
234
244
 
235
245
  msg = 'must assign iterable to extended slice'
@@ -259,18 +269,18 @@ class CA[I]():
259
269
  del item_list[idx]
260
270
  _ca = CA(item_list)
261
271
  (
262
- self._items,
263
- self._cnt,
264
- self._cap,
265
- self._front,
266
- self._rear,
272
+ self._items,
273
+ self._cnt,
274
+ self._cap,
275
+ self._front,
276
+ self._rear,
267
277
  ) = (
268
- _ca._items,
269
- _ca._cnt,
270
- _ca._cap,
271
- _ca._front,
272
- _ca._rear,
273
- )
278
+ _ca._items,
279
+ _ca._cnt,
280
+ _ca._cap,
281
+ _ca._front,
282
+ _ca._rear,
283
+ )
274
284
  del _ca
275
285
 
276
286
  def __eq__(self, other: object) -> bool:
@@ -280,28 +290,34 @@ class CA[I]():
280
290
  return False
281
291
 
282
292
  (
283
- front1,
284
- count1,
285
- capacity1,
286
- front2,
287
- count2,
288
- capacity2,
293
+ front1,
294
+ cnt1,
295
+ capacity1,
296
+ front2,
297
+ cnt2,
298
+ capacity2,
289
299
  ) = (
290
- self._front,
291
- self._cnt,
292
- self._cap,
293
- other._front,
294
- other._cnt,
295
- other._cap,
296
- )
297
-
298
- if count1 != count2:
300
+ self._front,
301
+ self._cnt,
302
+ self._cap,
303
+ other._front,
304
+ other._cnt,
305
+ other._cap,
306
+ )
307
+
308
+ if cnt1 != cnt2:
299
309
  return False
300
310
 
301
- for nn in range(count1):
302
- if self._items[(front1 + nn) % capacity1] is other._items[(front2 + nn) % capacity2]:
311
+ for nn in range(cnt1):
312
+ if (
313
+ self._items[(front1 + nn) % capacity1]
314
+ is other._items[(front2 + nn) % capacity2]
315
+ ):
303
316
  continue
304
- if self._items[(front1 + nn) % capacity1] != other._items[(front2 + nn) % capacity2]:
317
+ if (
318
+ self._items[(front1 + nn) % capacity1]
319
+ != other._items[(front2 + nn) % capacity2]
320
+ ):
305
321
  return False
306
322
  return True
307
323
 
@@ -314,14 +330,14 @@ class CA[I]():
314
330
  if self._cnt == self._cap:
315
331
  self._double_storage_capacity()
316
332
  (
317
- self._front,
318
- self._items[self._front],
319
- self._cnt,
333
+ self._front,
334
+ self._items[self._front],
335
+ self._cnt,
320
336
  ) = (
321
- (self._front - 1) % self._cap,
322
- item,
323
- self._cnt + 1,
324
- )
337
+ (self._front - 1) % self._cap,
338
+ item,
339
+ self._cnt + 1,
340
+ )
325
341
 
326
342
  def pushr(self, *items: I) -> None:
327
343
  """Push ``items`` on from right.
@@ -332,14 +348,14 @@ class CA[I]():
332
348
  if self._cnt == self._cap:
333
349
  self._double_storage_capacity()
334
350
  (
335
- self._rear,
336
- self._items[self._rear],
337
- self._cnt,
351
+ self._rear,
352
+ self._items[self._rear],
353
+ self._cnt,
338
354
  ) = (
339
- (self._rear + 1) % self._cap,
340
- item,
341
- self._cnt + 1,
342
- )
355
+ (self._rear + 1) % self._cap,
356
+ item,
357
+ self._cnt + 1,
358
+ )
343
359
 
344
360
  def popl(self) -> I:
345
361
  """Pop single item off from left side.
@@ -349,30 +365,30 @@ class CA[I]():
349
365
  """
350
366
  if self._cnt > 1:
351
367
  (
352
- d,
353
- self._items[self._front],
354
- self._front,
355
- self._cnt,
368
+ d,
369
+ self._items[self._front],
370
+ self._front,
371
+ self._cnt,
356
372
  ) = (
357
- self._items[self._front],
358
- None,
359
- (self._front + 1) % self._cap,
360
- self._cnt - 1,
361
- )
373
+ self._items[self._front],
374
+ nada,
375
+ (self._front + 1) % self._cap,
376
+ self._cnt - 1,
377
+ )
362
378
  elif self._cnt == 1:
363
379
  (
364
- d,
365
- self._items[self._front],
366
- self._cnt,
367
- self._front,
368
- self._rear,
380
+ d,
381
+ self._items[self._front],
382
+ self._cnt,
383
+ self._front,
384
+ self._rear,
369
385
  ) = (
370
- self._items[self._front],
371
- None,
372
- 0,
373
- 0,
374
- self._cap - 1,
375
- )
386
+ self._items[self._front],
387
+ nada,
388
+ 0,
389
+ 0,
390
+ self._cap - 1,
391
+ )
376
392
  else:
377
393
  msg = 'Method popl called on an empty CA'
378
394
  raise ValueError(msg)
@@ -386,30 +402,30 @@ class CA[I]():
386
402
  """
387
403
  if self._cnt > 1:
388
404
  (
389
- d,
390
- self._items[self._rear],
391
- self._rear,
392
- self._cnt,
405
+ d,
406
+ self._items[self._rear],
407
+ self._rear,
408
+ self._cnt,
393
409
  ) = (
394
- self._items[self._rear],
395
- None,
396
- (self._rear - 1) % self._cap,
397
- self._cnt - 1,
398
- )
410
+ self._items[self._rear],
411
+ nada,
412
+ (self._rear - 1) % self._cap,
413
+ self._cnt - 1,
414
+ )
399
415
  elif self._cnt == 1:
400
416
  (
401
- d,
402
- self._items[self._front],
403
- self._cnt,
404
- self._front,
405
- self._rear,
417
+ d,
418
+ self._items[self._front],
419
+ self._cnt,
420
+ self._front,
421
+ self._rear,
406
422
  ) = (
407
- self._items[self._front],
408
- None,
409
- 0,
410
- 0,
411
- self._cap - 1,
412
- )
423
+ self._items[self._front],
424
+ nada,
425
+ 0,
426
+ 0,
427
+ self._cap - 1,
428
+ )
413
429
  else:
414
430
  msg = 'Method popr called on an empty CA'
415
431
  raise ValueError(msg)
@@ -493,7 +509,7 @@ class CA[I]():
493
509
  for _ in range(n, 0, -1):
494
510
  self.pushl(self.popr())
495
511
 
496
- def map[U](self, f: Callable[[I], U]) -> "CA[U]":
512
+ def map[U](self, f: Callable[[I], U]) -> 'CA[U]':
497
513
  """Apply function ``f`` over the circular array's contents.
498
514
 
499
515
  :param f: Callable from type ``I`` to type ``U``.
@@ -501,52 +517,62 @@ class CA[I]():
501
517
  """
502
518
  return CA(map(f, self))
503
519
 
504
- def foldl[L](self, f: Callable[[L, I], L], start: L | None = None) -> L:
520
+ @overload
521
+ def foldl[L](self, f: Callable[[I, I], I]) -> I: ...
522
+ @overload
523
+ def foldl[L](self, f: Callable[[L, I], L], start: L) -> L: ...
524
+
525
+ def foldl[L](self, f: Callable[[L, I], L], start: L | NoValue = NoValue()) -> L:
505
526
  """Fold left with a function and optional starting item.
506
527
 
507
528
  :param f: Folding function, first argument to ``f`` is for the accumulator.
508
- :param start: Optional starting item.
529
+ :param start: "Optional" starting item.
509
530
  :returns: Reduced value produced by the left fold.
510
531
  :raises ValueError: When circular array empty and no starting item given.
511
532
  """
512
533
  if self._cnt == 0:
513
- if start is None:
534
+ if start is nada:
514
535
  msg = 'Method foldl called on an empty CA without a start item.'
515
536
  raise ValueError(msg)
516
- return start
537
+ return cast(L, start)
517
538
 
518
- if start is None:
539
+ if start is nada:
519
540
  acc = cast(L, self[0]) # in this case D = L
520
541
  for idx in range(1, self._cnt):
521
542
  acc = f(acc, self[idx])
522
543
  return acc
523
544
 
524
- acc = start
545
+ acc = cast(L, start)
525
546
  for d in self:
526
547
  acc = f(acc, d)
527
548
  return acc
528
549
 
529
- def foldr[R](self, f: Callable[[I, R], R], start: R | None = None) -> R:
550
+ @overload
551
+ def foldr[R](self, f: Callable[[I, I], I]) -> I: ...
552
+ @overload
553
+ def foldr[R](self, f: Callable[[I, R], R], start: R) -> R: ...
554
+
555
+ def foldr[R](self, f: Callable[[I, R], R], start: R | NoValue = nada) -> R:
530
556
  """Fold right with a function and an optional starting item.
531
557
 
532
558
  :param f: Folding function, second argument to ``f`` is for the accumulator.
533
- :param start: Optional starting item.
559
+ :param start: "Optional" starting item.
534
560
  :returns: Reduced value produced by the right fold.
535
561
  :raises ValueError: When circular array empty and no starting item given.
536
562
  """
537
563
  if self._cnt == 0:
538
- if start is None:
564
+ if start is nada:
539
565
  msg = 'Method foldr called on empty CA without initial value.'
540
566
  raise ValueError(msg)
541
- return start
567
+ return cast(R, start)
542
568
 
543
- if start is None:
569
+ if start is nada:
544
570
  acc = cast(R, self[-1]) # in this case D = R
545
571
  for idx in range(self._cnt - 2, -1, -1):
546
572
  acc = f(self[idx], acc)
547
573
  return acc
548
574
 
549
- acc = start
575
+ acc = cast(R, start)
550
576
  for d in reversed(self):
551
577
  acc = f(d, acc)
552
578
  return acc
@@ -561,16 +587,16 @@ class CA[I]():
561
587
  def empty(self) -> None:
562
588
  """Empty the circular array, keep current storage capacity."""
563
589
  (
564
- self._items,
565
- self._front,
566
- self._rear,
567
- self._cnt,
590
+ self._items,
591
+ self._front,
592
+ self._rear,
593
+ self._cnt,
568
594
  ) = (
569
- [None] * self._cap,
570
- 0,
571
- self._cap - 1,
572
- 0,
573
- )
595
+ [nada] * self._cap,
596
+ 0,
597
+ self._cap - 1,
598
+ 0,
599
+ )
574
600
 
575
601
  def fraction_filled(self) -> float:
576
602
  """Find fraction of the storage capacity which is filled.
@@ -589,12 +615,12 @@ class CA[I]():
589
615
  self._compact_storage_capacity()
590
616
  if (min_cap := minimum_capacity) > self._cap:
591
617
  (
592
- self._cap,
593
- self._items,
618
+ self._cap,
619
+ self._items,
594
620
  ) = (
595
- min_cap,
596
- self._items + [None] * (min_cap - self._cap),
597
- )
621
+ min_cap,
622
+ self._items + [nada] * (min_cap - self._cap),
623
+ )
598
624
  if self._cnt == 0:
599
625
  self._front, self._rear = 0, self._cap - 1
600
626
 
@@ -18,89 +18,92 @@ 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
 
40
- def __init__(
41
- self,
42
- items: Iterable[I] | None = None,
43
- capacity: int = 2
44
- ) -> None:
43
+ def __init__(self, *items: Iterable[I], cap: int = 2) -> None:
45
44
  """
46
- :param items: Optional iterable to initial populate circular array.
47
- :param capacity: Fixed storage capacity of circular array.
48
- :raises TypeError: When ``items`` not Iterable,
45
+ :param items: "Optionally" takes a single iterable to populate circular array.
46
+ :param cap: Minimum fixed storage capacity of circular array.
47
+ :raises TypeError: When ``items[0]`` not iterable,
48
+ :raises ValueError: If more than 1 iterable is given.
49
49
  """
50
- capacity = max(2, capacity)
51
- if items is None:
52
- self._items: list[I | None] = [None]*capacity
53
- count = 0
50
+ cap = max(2, cap)
51
+ if (size := len(items)) > 1:
52
+ msg = f'CAF expects at most 1 argument, got {size}'
53
+ raise ValueError(msg)
54
+ if size:
55
+ values: list[I | NoValue] = list(cast(Iterable[I | NoValue], items[0]))
56
+ cnt = len(values)
57
+ cap = max(cnt, cap)
58
+ self._items = values + [nada] * (cap - cnt)
54
59
  else:
55
- dlist: list[I | None] = list(items)
56
- count = len(dlist)
57
- capacity = max(count, capacity)
58
- self._items = dlist + [None]*(capacity - count)
59
- self._cap: Final[int] = capacity
60
- self._cnt = count
61
- if count == 0:
60
+ self._items = [nada] * cap
61
+ cnt = 0
62
+ self._cap: Final[int] = cap
63
+ self._cnt = cnt
64
+ if cnt == 0:
62
65
  self._front = 0
63
- self._rear = capacity - 1
66
+ self._rear = cap - 1
64
67
  else:
65
68
  self._front = 0
66
- self._rear = count - 1
69
+ self._rear = cnt - 1
67
70
 
68
71
  def __iter__(self) -> Iterator[I]:
69
72
  if self._cnt > 0:
70
73
  (
71
- capacity,
72
- rear,
73
- position,
74
- current_state,
74
+ cap,
75
+ rear,
76
+ position,
77
+ current_state,
75
78
  ) = (
76
- self._cap,
77
- self._rear,
78
- self._front,
79
- self._items.copy(),
80
- )
79
+ self._cap,
80
+ self._rear,
81
+ self._front,
82
+ self._items.copy(),
83
+ )
81
84
 
82
85
  while position != rear:
83
86
  yield cast(I, current_state[position])
84
- position = (position + 1) % capacity
87
+ position = (position + 1) % cap
85
88
  yield cast(I, current_state[position])
86
89
 
87
90
  def __reversed__(self) -> Iterator[I]:
88
91
  if self._cnt > 0:
89
92
  (
90
- capacity,
91
- front,
92
- position,
93
- current_state,
93
+ cap,
94
+ front,
95
+ position,
96
+ current_state,
94
97
  ) = (
95
- self._cap,
96
- self._front,
97
- self._rear,
98
- self._items.copy(),
99
- )
98
+ self._cap,
99
+ self._front,
100
+ self._rear,
101
+ self._items.copy(),
102
+ )
100
103
 
101
104
  while position != front:
102
105
  yield cast(I, current_state[position])
103
- position = (position - 1) % capacity
106
+ position = (position - 1) % cap
104
107
  yield cast(I, current_state[position])
105
108
 
106
109
  def __repr__(self) -> str:
@@ -150,18 +153,18 @@ class CAF[I]():
150
153
  def __delitem__(self, idx: int) -> None:
151
154
  item_list = list(self)
152
155
  del item_list[idx]
153
- _ca = CAF(item_list, self._cap)
156
+ _ca = CAF(item_list, cap = self._cap)
154
157
  (
155
- self._items,
156
- self._cnt,
157
- self._front,
158
- self._rear,
158
+ self._items,
159
+ self._cnt,
160
+ self._front,
161
+ self._rear,
159
162
  ) = (
160
- _ca._items,
161
- _ca._cnt,
162
- _ca._front,
163
- _ca._rear,
164
- )
163
+ _ca._items,
164
+ _ca._cnt,
165
+ _ca._front,
166
+ _ca._rear,
167
+ )
165
168
  del _ca
166
169
 
167
170
  def __eq__(self, other: object) -> bool:
@@ -171,28 +174,34 @@ class CAF[I]():
171
174
  return False
172
175
 
173
176
  (
174
- front1,
175
- count1,
176
- capacity1,
177
- front2,
178
- count2,
179
- capacity2,
177
+ front1,
178
+ cnt1,
179
+ cap1,
180
+ front2,
181
+ cnt2,
182
+ cap2,
180
183
  ) = (
181
- self._front,
182
- self._cnt,
183
- self._cap,
184
- other._front,
185
- other._cnt,
186
- other._cap,
187
- )
188
-
189
- if count1 != count2:
184
+ self._front,
185
+ self._cnt,
186
+ self._cap,
187
+ other._front,
188
+ other._cnt,
189
+ other._cap,
190
+ )
191
+
192
+ if cnt1 != cnt2:
190
193
  return False
191
194
 
192
- for nn in range(count1):
193
- if self._items[(front1 + nn) % capacity1] is other._items[(front2 + nn) % capacity2]:
195
+ for nn in range(cnt1):
196
+ if (
197
+ self._items[(front1 + nn) % cap1]
198
+ is other._items[(front2 + nn) % cap2]
199
+ ):
194
200
  continue
195
- if self._items[(front1 + nn) % capacity1] != other._items[(front2 + nn) % capacity2]:
201
+ if (
202
+ self._items[(front1 + nn) % cap1]
203
+ != other._items[(front2 + nn) % cap2]
204
+ ):
196
205
  return False
197
206
  return True
198
207
 
@@ -207,14 +216,14 @@ class CAF[I]():
207
216
  raise ValueError(msg)
208
217
 
209
218
  (
210
- self._front,
211
- self._items[self._front],
212
- self._cnt,
219
+ self._front,
220
+ self._items[self._front],
221
+ self._cnt,
213
222
  ) = (
214
- (self._front - 1) % self._cap,
215
- item,
216
- self._cnt + 1,
217
- )
223
+ (self._front - 1) % self._cap,
224
+ item,
225
+ self._cnt + 1,
226
+ )
218
227
 
219
228
  def pushr(self, item: I) -> None:
220
229
  """Push ``item`` on from Right.
@@ -227,14 +236,14 @@ class CAF[I]():
227
236
  raise ValueError(msg)
228
237
 
229
238
  (
230
- self._rear,
231
- self._items[self._rear],
232
- self._cnt,
239
+ self._rear,
240
+ self._items[self._rear],
241
+ self._cnt,
233
242
  ) = (
234
- (self._rear + 1) % self._cap,
235
- item,
236
- self._cnt + 1,
237
- )
243
+ (self._rear + 1) % self._cap,
244
+ item,
245
+ self._cnt + 1,
246
+ )
238
247
 
239
248
  def popl(self) -> I:
240
249
  """Pop single item off from left side.
@@ -244,30 +253,30 @@ class CAF[I]():
244
253
  """
245
254
  if self._cnt > 1:
246
255
  (
247
- d,
248
- self._items[self._front],
249
- self._front,
250
- self._cnt,
256
+ d,
257
+ self._items[self._front],
258
+ self._front,
259
+ self._cnt,
251
260
  ) = (
252
- self._items[self._front],
253
- None,
254
- (self._front + 1) % self._cap,
255
- self._cnt - 1,
256
- )
261
+ self._items[self._front],
262
+ nada,
263
+ (self._front + 1) % self._cap,
264
+ self._cnt - 1,
265
+ )
257
266
  elif self._cnt == 1:
258
267
  (
259
- d,
260
- self._items[self._front],
261
- self._cnt,
262
- self._front,
263
- self._rear,
268
+ d,
269
+ self._items[self._front],
270
+ self._cnt,
271
+ self._front,
272
+ self._rear,
264
273
  ) = (
265
- self._items[self._front],
266
- None,
267
- 0,
268
- 0,
269
- self._cap - 1,
270
- )
274
+ self._items[self._front],
275
+ nada,
276
+ 0,
277
+ 0,
278
+ self._cap - 1,
279
+ )
271
280
  else:
272
281
  msg = 'Method popl called on an empty CAF'
273
282
  raise ValueError(msg)
@@ -281,30 +290,30 @@ class CAF[I]():
281
290
  """
282
291
  if self._cnt > 1:
283
292
  (
284
- d,
285
- self._items[self._rear],
286
- self._rear,
287
- self._cnt,
293
+ d,
294
+ self._items[self._rear],
295
+ self._rear,
296
+ self._cnt,
288
297
  ) = (
289
- self._items[self._rear],
290
- None,
291
- (self._rear - 1) % self._cap,
292
- self._cnt - 1,
293
- )
298
+ self._items[self._rear],
299
+ nada,
300
+ (self._rear - 1) % self._cap,
301
+ self._cnt - 1,
302
+ )
294
303
  elif self._cnt == 1:
295
304
  (
296
- d,
297
- self._items[self._front],
298
- self._cnt,
299
- self._front,
300
- self._rear,
305
+ d,
306
+ self._items[self._front],
307
+ self._cnt,
308
+ self._front,
309
+ self._rear,
301
310
  ) = (
302
- self._items[self._front],
303
- None,
304
- 0,
305
- 0,
306
- self._cap - 1,
307
- )
311
+ self._items[self._front],
312
+ nada,
313
+ 0,
314
+ 0,
315
+ self._cap - 1,
316
+ )
308
317
  else:
309
318
  msg = 'Method popr called on an empty CAF'
310
319
  raise ValueError(msg)
@@ -388,15 +397,20 @@ class CAF[I]():
388
397
  for _ in range(n, 0, -1):
389
398
  self.pushl(self.popr())
390
399
 
391
- def map[U](self, f: Callable[[I], U]) -> "CAF[U]":
400
+ def map[U](self, f: Callable[[I], U]) -> 'CAF[U]':
392
401
  """Apply function ``f`` over the circular array's contents,
393
402
 
394
403
  :param f: Callable from type ``I`` to type ``U``.
395
404
  :returns: New fixed capacity circular array instance.
396
405
  """
397
- return CAF(map(f, self), self._cap)
406
+ return CAF(map(f, self), cap = self._cap)
407
+
408
+ @overload
409
+ def foldl[L](self, f: Callable[[I, I], I]) -> I: ...
410
+ @overload
411
+ def foldl[L](self, f: Callable[[L, I], L], start: L) -> L: ...
398
412
 
399
- def foldl[L](self, f: Callable[[L, I], L], start: L | None = None) -> L:
413
+ def foldl[L](self, f: Callable[[L, I], L], start: L | NoValue = nada) -> L:
400
414
  """Fold left with a function and optional stating item.
401
415
 
402
416
  :param f: Folding function, first argument to ``f`` is for the accumulator.
@@ -405,23 +419,28 @@ class CAF[I]():
405
419
  :raises ValueError: When circular array empty and no starting item given.
406
420
  """
407
421
  if self._cnt == 0:
408
- if start is None:
422
+ if start is nada:
409
423
  msg = 'Method foldl called on an empty CAF without a start item.'
410
424
  raise ValueError(msg)
411
- return start
425
+ return cast(L, start)
412
426
 
413
- if start is None:
427
+ if start is nada:
414
428
  acc = cast(L, self[0]) # in this case D = L
415
429
  for idx in range(1, self._cnt):
416
430
  acc = f(acc, self[idx])
417
431
  return acc
418
432
 
419
- acc = start
433
+ acc = cast(L, start)
420
434
  for d in self:
421
435
  acc = f(acc, d)
422
436
  return acc
423
437
 
424
- def foldr[R](self, f: Callable[[I, R], R], start: R | None = None) -> R:
438
+ @overload
439
+ def foldr[R](self, f: Callable[[I, I], I]) -> I: ...
440
+ @overload
441
+ def foldr[R](self, f: Callable[[I, R], R], start: R) -> R: ...
442
+
443
+ def foldr[R](self, f: Callable[[I, R], R], start: R | NoValue = nada) -> R:
425
444
  """Fold right with a function and an optional starting item.
426
445
 
427
446
  :param f: Folding function, second argument to ``f`` is for the accumulator.
@@ -430,18 +449,18 @@ class CAF[I]():
430
449
  :raises ValueError: When circular array empty and no starting item given.
431
450
  """
432
451
  if self._cnt == 0:
433
- if start is None:
452
+ if start is nada:
434
453
  msg = 'Method foldr called on empty CAF without initial value.'
435
454
  raise ValueError(msg)
436
- return start
455
+ return cast(R, start)
437
456
 
438
- if start is None:
457
+ if start is nada:
439
458
  acc = cast(R, self[-1]) # in this case D = R
440
459
  for idx in range(self._cnt - 2, -1, -1):
441
460
  acc = f(self[idx], acc)
442
461
  return acc
443
462
 
444
- acc = start
463
+ acc = cast(R, start)
445
464
  for d in reversed(self):
446
465
  acc = f(d, acc)
447
466
  return acc
@@ -456,16 +475,16 @@ class CAF[I]():
456
475
  def empty(self) -> None:
457
476
  """Empty the circular array."""
458
477
  (
459
- self._items,
460
- self._front,
461
- self._rear,
462
- self._cnt,
478
+ self._items,
479
+ self._front,
480
+ self._rear,
481
+ self._cnt,
463
482
  ) = (
464
- [None] * self._cap,
465
- 0,
466
- self._cap - 1,
467
- 0,
468
- )
483
+ [nada] * self._cap,
484
+ 0,
485
+ self._cap - 1,
486
+ 0,
487
+ )
469
488
 
470
489
  def fraction_filled(self) -> float:
471
490
  """Find fraction of the storage capacity which is filled.
@@ -475,11 +494,11 @@ class CAF[I]():
475
494
  return self._cnt / self._cap
476
495
 
477
496
 
478
- def caf[T](*items: T, capacity: int = 2) -> CAF[T]:
497
+ def caf[T](*items: T, cap: int = 2) -> CAF[T]:
479
498
  """Produce a circular array from a variable number of arguments.
480
499
 
481
500
  :param items: Initial items for a new fixed capacity :circular array.
482
- :param capacity: The minimum storage capacity to set.
501
+ :param cap: The minimum storage capacity to set.
483
502
  :returns: New fixed storage capacity circular array.
484
503
  """
485
- return CAF(items, capacity = capacity)
504
+ return CAF(items, cap=cap)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonic-fp-circulararray
3
- Version: 5.3.3
3
+ Version: 6.0.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,8 @@
1
+ pythonic_fp/circulararray/__init__.py,sha256=uR0KASmw1Zraz9l3VPbqPm9WUKzWk-0_tUGT0opabzQ,1515
2
+ pythonic_fp/circulararray/auto.py,sha256=7aT4l93zAPWIuIWjqxnLpXyJJZ349CCPqPGS-yXJxho,19486
3
+ pythonic_fp/circulararray/fixed.py,sha256=usGVbw2wy4idhhMSZq_DFzROTrXUeiCjVpMYfetXiWs,15456
4
+ pythonic_fp/circulararray/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ pythonic_fp_circulararray-6.0.0.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
6
+ pythonic_fp_circulararray-6.0.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
7
+ pythonic_fp_circulararray-6.0.0.dist-info/METADATA,sha256=W9GQEAUwxQi-yzQU2yGqhanDDXYe-piRV5ZxZJYpYpk,2761
8
+ pythonic_fp_circulararray-6.0.0.dist-info/RECORD,,
File without changes
@@ -1,43 +0,0 @@
1
- from collections.abc import Callable, Iterable, Iterator
2
- from typing import overload
3
-
4
- __all__ = ['CA', 'ca']
5
-
6
- class CA[I]:
7
- def __init__(self, items: Iterable[I] | None = None) -> None: ...
8
- def __iter__(self) -> Iterator[I]: ...
9
- def __reversed__(self) -> Iterator[I]: ...
10
- def __bool__(self) -> bool: ...
11
- def __len__(self) -> int: ...
12
- @overload
13
- def __getitem__(self, idx: int) -> I: ...
14
- @overload
15
- def __getitem__(self, idx: slice) -> CA[I]: ...
16
- @overload
17
- def __setitem__(self, idx: int, vals: I) -> None: ...
18
- @overload
19
- def __setitem__(self, idx: slice, vals: Iterable[I]) -> None: ...
20
- @overload
21
- def __delitem__(self, idx: int) -> None: ...
22
- @overload
23
- def __delitem__(self, idx: slice) -> None: ...
24
- def __eq__(self, other: object) -> bool: ...
25
- def pushl(self, *items: I) -> None: ...
26
- def pushr(self, *items: I) -> None: ...
27
- def popl(self) -> I: ...
28
- def popr(self) -> I: ...
29
- def popld(self, default: I) -> I: ...
30
- def poprd(self, default: I) -> I: ...
31
- def poplt(self, maximum: int) -> tuple[I, ...]: ...
32
- def poprt(self, maximum: int) -> tuple[I, ...]: ...
33
- def rotl(self, n: int = 1) -> None: ...
34
- def rotr(self, n: int = 1) -> None: ...
35
- 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: ...
38
- def capacity(self) -> int: ...
39
- def empty(self) -> None: ...
40
- def fraction_filled(self) -> float: ...
41
- def resize(self, minimum_capacity: int = 2) -> None: ...
42
-
43
- def ca[T](*items: T) -> CA[T]: ...
@@ -1,32 +0,0 @@
1
- from collections.abc import Callable, Iterable, Iterator
2
-
3
- __all__ = ['CAF', 'caf']
4
-
5
- class CAF[I]:
6
- def __init__(self, items: Iterable[I] | None = None, capacity: int = 2) -> None: ...
7
- def __iter__(self) -> Iterator[I]: ...
8
- def __reversed__(self) -> Iterator[I]: ...
9
- def __bool__(self) -> bool: ...
10
- def __len__(self) -> int: ...
11
- def __getitem__(self, idx: int) -> I: ...
12
- def __setitem__(self, idx: int, val: I) -> None: ...
13
- def __delitem__(self, idx: int) -> None: ...
14
- def __eq__(self, other: object) -> bool: ...
15
- def pushl(self, item: I) -> None: ...
16
- def pushr(self, item: I) -> None: ...
17
- def popl(self) -> I: ...
18
- def popr(self) -> I: ...
19
- def popld(self, default: I) -> I: ...
20
- def poprd(self, default: I) -> I: ...
21
- def poplt(self, maximum: int) -> tuple[I, ...]: ...
22
- def poprt(self, maximum: int) -> tuple[I, ...]: ...
23
- def rotl(self, n: int = 1) -> None: ...
24
- def rotr(self, n: int = 1) -> None: ...
25
- 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: ...
28
- def capacity(self) -> int: ...
29
- def empty(self) -> None: ...
30
- def fraction_filled(self) -> float: ...
31
-
32
- def caf[T](*items: T, capacity: int = 2) -> CAF[T]: ...
@@ -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,,