pythonic-fp-circulararray 5.3.2__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.
@@ -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: ...
@@ -16,50 +16,55 @@
16
16
  Fixed Storage Capacity
17
17
  ======================
18
18
 
19
- Circular array with fixed storage capacity.
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
- Basically a list that can be grown from both ends
47
- in O(1) time and space complexity.
48
-
49
- :param items: optional iterable to initial populate circular array
50
- :param capacity: fixed storage capacity of circular array
51
- :raises TypeError: if items is not Iterable
52
-
54
+ :param items: "Optional" iterable to initial populate circular array.
55
+ :param capacity: Minimum fixed storage capacity of circular array.
56
+ :raises TypeError: When ``items`` not Iterable,
53
57
  """
54
58
  capacity = max(2, capacity)
55
- if items is None:
56
- self._items: list[I | None] = [None]*capacity
59
+ if items is nada:
60
+ self._items: list[I | NoValue] = [nada] * capacity
57
61
  count = 0
58
62
  else:
59
- dlist: list[I | None] = list(items)
63
+ values: list[I | NoValue] = list(cast(Iterable[I | NoValue], items))
64
+ dlist: list[I | NoValue] = list(values)
60
65
  count = len(dlist)
61
66
  capacity = max(count, capacity)
62
- self._items = dlist + [None]*(capacity - count)
67
+ self._items = dlist + [nada] * (capacity - count)
63
68
  self._cap: Final[int] = capacity
64
69
  self._cnt = count
65
70
  if count == 0:
@@ -72,16 +77,16 @@ class CAF[I]():
72
77
  def __iter__(self) -> Iterator[I]:
73
78
  if self._cnt > 0:
74
79
  (
75
- capacity,
76
- rear,
77
- position,
78
- current_state,
80
+ capacity,
81
+ rear,
82
+ position,
83
+ current_state,
79
84
  ) = (
80
- self._cap,
81
- self._rear,
82
- self._front,
83
- self._items.copy(),
84
- )
85
+ self._cap,
86
+ self._rear,
87
+ self._front,
88
+ self._items.copy(),
89
+ )
85
90
 
86
91
  while position != rear:
87
92
  yield cast(I, current_state[position])
@@ -91,16 +96,16 @@ class CAF[I]():
91
96
  def __reversed__(self) -> Iterator[I]:
92
97
  if self._cnt > 0:
93
98
  (
94
- capacity,
95
- front,
96
- position,
97
- current_state,
99
+ capacity,
100
+ front,
101
+ position,
102
+ current_state,
98
103
  ) = (
99
- self._cap,
100
- self._front,
101
- self._rear,
102
- self._items.copy(),
103
- )
104
+ self._cap,
105
+ self._front,
106
+ self._rear,
107
+ self._items.copy(),
108
+ )
104
109
 
105
110
  while position != front:
106
111
  yield cast(I, current_state[position])
@@ -156,16 +161,16 @@ class CAF[I]():
156
161
  del item_list[idx]
157
162
  _ca = CAF(item_list, self._cap)
158
163
  (
159
- self._items,
160
- self._cnt,
161
- self._front,
162
- self._rear,
164
+ self._items,
165
+ self._cnt,
166
+ self._front,
167
+ self._rear,
163
168
  ) = (
164
- _ca._items,
165
- _ca._cnt,
166
- _ca._front,
167
- _ca._rear,
168
- )
169
+ _ca._items,
170
+ _ca._cnt,
171
+ _ca._front,
172
+ _ca._rear,
173
+ )
169
174
  del _ca
170
175
 
171
176
  def __eq__(self, other: object) -> bool:
@@ -175,161 +180,157 @@ class CAF[I]():
175
180
  return False
176
181
 
177
182
  (
178
- front1,
179
- count1,
180
- capacity1,
181
- front2,
182
- count2,
183
- capacity2,
183
+ front1,
184
+ count1,
185
+ capacity1,
186
+ front2,
187
+ count2,
188
+ capacity2,
184
189
  ) = (
185
- self._front,
186
- self._cnt,
187
- self._cap,
188
- other._front,
189
- other._cnt,
190
- other._cap,
191
- )
190
+ self._front,
191
+ self._cnt,
192
+ self._cap,
193
+ other._front,
194
+ other._cnt,
195
+ other._cap,
196
+ )
192
197
 
193
198
  if count1 != count2:
194
199
  return False
195
200
 
196
201
  for nn in range(count1):
197
- 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
+ ):
198
206
  continue
199
- 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
+ ):
200
211
  return False
201
212
  return True
202
213
 
203
214
  def pushl(self, item: I) -> None:
204
- """
205
- Push single item onto left side (front) of circular array.
206
-
207
- :param item: single item pushed onto circular array from left
208
- :raises ValueError: when called on a full CAF
215
+ """Push ``item`` on from left.
209
216
 
217
+ :param item: Single item pushed onto circular array from left (front).
218
+ :raises ValueError: When called on a full ``CAF``.
210
219
  """
211
220
  if self._cnt == self._cap:
212
221
  msg = 'Method pushl called on a full CAF'
213
222
  raise ValueError(msg)
214
223
 
215
224
  (
216
- self._front,
217
- self._items[self._front],
218
- self._cnt,
225
+ self._front,
226
+ self._items[self._front],
227
+ self._cnt,
219
228
  ) = (
220
- (self._front - 1) % self._cap,
221
- item,
222
- self._cnt + 1,
223
- )
229
+ (self._front - 1) % self._cap,
230
+ item,
231
+ self._cnt + 1,
232
+ )
224
233
 
225
234
  def pushr(self, item: I) -> None:
226
- """
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
235
+ """Push ``item`` on from Right.
231
236
 
237
+ :param item: Single ``item`` pushed onto circular array from right (rear).
238
+ :raises ValueError: When called on a full fixed storage capacity circular array.
232
239
  """
233
240
  if self._cnt == self._cap:
234
241
  msg = 'Method pushr called on a full CAF'
235
242
  raise ValueError(msg)
236
243
 
237
244
  (
238
- self._rear,
239
- self._items[self._rear],
240
- self._cnt,
245
+ self._rear,
246
+ self._items[self._rear],
247
+ self._cnt,
241
248
  ) = (
242
- (self._rear + 1) % self._cap,
243
- item,
244
- self._cnt + 1,
245
- )
249
+ (self._rear + 1) % self._cap,
250
+ item,
251
+ self._cnt + 1,
252
+ )
246
253
 
247
254
  def popl(self) -> I:
248
- """
249
- Pop item off left side (front) of circular array.
250
-
251
- :returns: item popped from left side of circular array
252
- :raises ValueError: when called on an empty circular array
255
+ """Pop single item off from left side.
253
256
 
257
+ :returns: Item popped from left side (front) of circular array.
258
+ :raises ValueError: When called on an empty circular array.
254
259
  """
255
260
  if self._cnt > 1:
256
261
  (
257
- d,
258
- self._items[self._front],
259
- self._front,
260
- self._cnt,
262
+ d,
263
+ self._items[self._front],
264
+ self._front,
265
+ self._cnt,
261
266
  ) = (
262
- self._items[self._front],
263
- None,
264
- (self._front + 1) % self._cap,
265
- self._cnt - 1,
266
- )
267
+ self._items[self._front],
268
+ nada,
269
+ (self._front + 1) % self._cap,
270
+ self._cnt - 1,
271
+ )
267
272
  elif self._cnt == 1:
268
273
  (
269
- d,
270
- self._items[self._front],
271
- self._cnt,
272
- self._front,
273
- self._rear,
274
+ d,
275
+ self._items[self._front],
276
+ self._cnt,
277
+ self._front,
278
+ self._rear,
274
279
  ) = (
275
- self._items[self._front],
276
- None,
277
- 0,
278
- 0,
279
- self._cap - 1,
280
- )
280
+ self._items[self._front],
281
+ nada,
282
+ 0,
283
+ 0,
284
+ self._cap - 1,
285
+ )
281
286
  else:
282
287
  msg = 'Method popl called on an empty CAF'
283
288
  raise ValueError(msg)
284
289
  return cast(I, d)
285
290
 
286
291
  def popr(self) -> I:
287
- """
288
- Pop item off right side (rear) of circular array.
289
-
290
- :returns: item popped from right side of circular array
291
- :raises ValueError: when called on an empty circular array
292
+ """Pop single item off from right side.
292
293
 
294
+ :returns: Item popped from right side (rear) of circular array.
295
+ :raises ValueError: When called on an empty circular array.
293
296
  """
294
297
  if self._cnt > 1:
295
298
  (
296
- d,
297
- self._items[self._rear],
298
- self._rear,
299
- self._cnt,
299
+ d,
300
+ self._items[self._rear],
301
+ self._rear,
302
+ self._cnt,
300
303
  ) = (
301
- self._items[self._rear],
302
- None,
303
- (self._rear - 1) % self._cap,
304
- self._cnt - 1,
305
- )
304
+ self._items[self._rear],
305
+ nada,
306
+ (self._rear - 1) % self._cap,
307
+ self._cnt - 1,
308
+ )
306
309
  elif self._cnt == 1:
307
310
  (
308
- d,
309
- self._items[self._front],
310
- self._cnt,
311
- self._front,
312
- self._rear,
311
+ d,
312
+ self._items[self._front],
313
+ self._cnt,
314
+ self._front,
315
+ self._rear,
313
316
  ) = (
314
- self._items[self._front],
315
- None,
316
- 0,
317
- 0,
318
- self._cap - 1,
319
- )
317
+ self._items[self._front],
318
+ nada,
319
+ 0,
320
+ 0,
321
+ self._cap - 1,
322
+ )
320
323
  else:
321
324
  msg = 'Method popr called on an empty CAF'
322
325
  raise ValueError(msg)
323
326
  return cast(I, d)
324
327
 
325
328
  def popld(self, default: I) -> I:
326
- """
327
- Pop one item from left side of the circular array, provide
329
+ """Pop one item from left side of the circular array, provide
328
330
  a mandatory default value. "Safe" version of popl.
329
331
 
330
- :param default: item returned if circular array is empty
331
- :returns: item popped from left side or default item if empty
332
-
332
+ :param default: Item returned if circular array is empty.
333
+ :returns: Item popped from left side or default item if empty.
333
334
  """
334
335
  try:
335
336
  return self.popl()
@@ -337,13 +338,11 @@ class CAF[I]():
337
338
  return default
338
339
 
339
340
  def poprd(self, default: I) -> I:
340
- """
341
- Pop one item from right side of the circular array, provide
341
+ """Pop one item from right side of the circular array, provide
342
342
  a mandatory default value. "Safe" version of popr.
343
343
 
344
- :param default: item returned if circular array is empty
345
- :returns: item popped from right side or default item if empty
346
-
344
+ :param default: Item returned if circular array is empty.
345
+ :returns: Item popped from right side or default item if empty.
347
346
  """
348
347
  try:
349
348
  return self.popr()
@@ -351,12 +350,10 @@ class CAF[I]():
351
350
  return default
352
351
 
353
352
  def poplt(self, maximum: int) -> tuple[I, ...]:
354
- """
355
- Pop multiple items from left side of circular array.
356
-
357
- :param maximum: maximum number of items to pop, may pop less if not enough items
358
- :returns: items in the order popped, left to right
353
+ """Pop multiple items from left side of circular array.
359
354
 
355
+ :param maximum: Maximum number of items to pop, may pop less if not enough items.
356
+ :returns: Tuple of items in the order popped, left to right.
360
357
  """
361
358
  item_list: list[I] = []
362
359
 
@@ -371,12 +368,10 @@ class CAF[I]():
371
368
  return tuple(item_list)
372
369
 
373
370
  def poprt(self, maximum: int) -> tuple[I, ...]:
374
- """
375
- Pop multiple items from right side of circular array.
376
-
377
- :param maximum: maximum number of items to pop, may pop less if not enough items
378
- :returns: items in the order popped, right to left
371
+ """Pop multiple items from right side of circular array.
379
372
 
373
+ :param maximum: Maximum number of items to pop, may pop less if not enough items.
374
+ :returns: Tuple of items in the order popped, right to left.
380
375
  """
381
376
  item_list: list[I] = []
382
377
  while maximum > 0:
@@ -389,11 +384,9 @@ class CAF[I]():
389
384
  return tuple(item_list)
390
385
 
391
386
  def rotl(self, n: int = 1) -> None:
392
- """
393
- Rotate items to the left.
394
-
395
- :param n: number of times to shift elements to the left
387
+ """Rotate items to the left.
396
388
 
389
+ :param n: Number of times to shift elements to the left.
397
390
  """
398
391
  if self._cnt < 2:
399
392
  return
@@ -401,122 +394,117 @@ class CAF[I]():
401
394
  self.pushr(self.popl())
402
395
 
403
396
  def rotr(self, n: int = 1) -> None:
404
- """
405
- Rotate items to the right.
406
-
407
- :param n: number of times to shift elements to the right
397
+ """Rotate items to the right.
408
398
 
399
+ :param n: Number of times to shift elements to the right.
409
400
  """
410
401
  if self._cnt < 2:
411
402
  return
412
403
  for _ in range(n, 0, -1):
413
404
  self.pushl(self.popr())
414
405
 
415
- def map[U](self, f: Callable[[I], U]) -> "CAF[U]":
416
- """
417
- Apply function ``f`` over the circular array's contents,
418
-
419
- :param f: callable from type I to type U
420
- :returns: new fixed circular array instance
406
+ def map[U](self, f: Callable[[I], U]) -> 'CAF[U]':
407
+ """Apply function ``f`` over the circular array's contents,
421
408
 
409
+ :param f: Callable from type ``I`` to type ``U``.
410
+ :returns: New fixed capacity circular array instance.
422
411
  """
423
412
  return CAF(map(f, self), self._cap)
424
413
 
425
- def foldl[L](self, f: Callable[[L, I], L], start: L | None = None) -> L:
426
- """
427
- Fold left with a function and optional stating item.
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: ...
428
418
 
429
- :param f: first argument to f is for the accumulator
430
- :param start: optional starting item
431
- :returns: reduced value produced by the left fold
432
- :raises ValueError: when circular array empty and no starting item given
419
+ def foldl[L](self, f: Callable[[L, I], L], start: L | NoValue = nada) -> L:
420
+ """Fold left with a function and optional stating item.
433
421
 
422
+ :param f: Folding function, first argument to ``f`` is for the accumulator.
423
+ :param start: Optional starting item.
424
+ :returns: Reduced value produced by the left fold.
425
+ :raises ValueError: When circular array empty and no starting item given.
434
426
  """
435
427
  if self._cnt == 0:
436
- if start is None:
428
+ if start is nada:
437
429
  msg = 'Method foldl called on an empty CAF without a start item.'
438
430
  raise ValueError(msg)
439
- return start
431
+ return cast(L, start)
440
432
 
441
- if start is None:
433
+ if start is nada:
442
434
  acc = cast(L, self[0]) # in this case D = L
443
435
  for idx in range(1, self._cnt):
444
436
  acc = f(acc, self[idx])
445
437
  return acc
446
438
 
447
- acc = start
439
+ acc = cast(L, start)
448
440
  for d in self:
449
441
  acc = f(acc, d)
450
442
  return acc
451
443
 
452
- def foldr[R](self, f: Callable[[I, R], R], start: R | None = None) -> R:
453
- """
454
- Fold right with a function and an optional starting item.
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: ...
455
448
 
456
- :param f: second argument to f is for the accumulator
457
- :param start: optional starting item
458
- :returns: reduced value produced by the right fold
459
- :raises ValueError: when circular array empty and no starting item given
449
+ def foldr[R](self, f: Callable[[I, R], R], start: R | NoValue = nada) -> R:
450
+ """Fold right with a function and an optional starting item.
460
451
 
452
+ :param f: Folding function, second argument to ``f`` is for the accumulator.
453
+ :param start: Optional starting item.
454
+ :returns: Reduced value produced by the right fold.
455
+ :raises ValueError: When circular array empty and no starting item given.
461
456
  """
462
457
  if self._cnt == 0:
463
- if start is None:
458
+ if start is nada:
464
459
  msg = 'Method foldr called on empty CAF without initial value.'
465
460
  raise ValueError(msg)
466
- return start
461
+ return cast(R, start)
467
462
 
468
- if start is None:
463
+ if start is nada:
469
464
  acc = cast(R, self[-1]) # in this case D = R
470
465
  for idx in range(self._cnt - 2, -1, -1):
471
466
  acc = f(self[idx], acc)
472
467
  return acc
473
468
 
474
- acc = start
469
+ acc = cast(R, start)
475
470
  for d in reversed(self):
476
471
  acc = f(d, acc)
477
472
  return acc
478
473
 
479
474
  def capacity(self) -> int:
480
- """
481
- Return fixed storage capacity of the circular array.
482
-
483
- :returns: fixed storage capacity
475
+ """Return fixed storage capacity of the circular array.
484
476
 
477
+ :returns: Fixed storage capacity.
485
478
  """
486
479
  return self._cap
487
480
 
488
481
  def empty(self) -> None:
489
- """
490
- Empty the circular array."""
482
+ """Empty the circular array."""
491
483
  (
492
- self._items,
493
- self._front,
494
- self._rear,
495
- self._cnt,
484
+ self._items,
485
+ self._front,
486
+ self._rear,
487
+ self._cnt,
496
488
  ) = (
497
- [None] * self._cap,
498
- 0,
499
- self._cap - 1,
500
- 0,
501
- )
489
+ [nada] * self._cap,
490
+ 0,
491
+ self._cap - 1,
492
+ 0,
493
+ )
502
494
 
503
495
  def fraction_filled(self) -> float:
504
- """
505
- Find fraction of the storage capacity which is filled.
506
-
507
- :returns: the ratio count/capacity
496
+ """Find fraction of the storage capacity which is filled.
508
497
 
498
+ :returns: The ratio count/capacity.
509
499
  """
510
500
  return self._cnt / self._cap
511
501
 
512
502
 
513
503
  def caf[T](*items: T, capacity: int = 2) -> CAF[T]:
514
- """
515
- Produce a circular array from a variable number of arguments.
516
-
517
- :param items: initial items for a new circular array
518
- :param capacity: the minimum storage capacity to set
519
- :returns: new fixed storage capacity circular array
504
+ """Produce a circular array from a variable number of arguments.
520
505
 
506
+ :param items: Initial items for a new fixed capacity :circular array.
507
+ :param capacity: The minimum storage capacity to set.
508
+ :returns: New fixed storage capacity circular array.
521
509
  """
522
- return CAF(items, capacity = capacity)
510
+ return CAF(items, capacity=capacity)