pythonic-fp-circulararray 6.0.4__py3-none-any.whl → 6.1.1__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.
@@ -12,19 +12,6 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- """
16
- .. admonition:: Fixed storage capacity circular array CAF
17
-
18
- - O(1) pops and pushes either end
19
- - O(1) indexing, does not support slicing
20
- - fixed total storage capacity
21
- - iterable, safely mutates while iterators iterating over previous state
22
- - comparisons compare identity before equality, like builtins
23
- - in boolean context, falsy when either empty or full, otherwise truthy
24
- - function ``caf`` produces fixed capacity circular array from arguments
25
-
26
- """
27
-
28
15
  from collections.abc import Callable, Iterable, Iterator
29
16
  from typing import cast, Final, overload
30
17
  from pythonic_fp.gadgets.sentinels.novalue import NoValue
@@ -35,19 +22,38 @@ nada: Final[NoValue] = NoValue()
35
22
 
36
23
 
37
24
  class CAF[X]:
25
+
26
+ """
27
+ .. admonition:: Fixed storage capacity circular array CAF
28
+
29
+ - O(1) pops and pushes either end
30
+ - O(1) indexing, does not support slicing
31
+ - fixed total storage capacity
32
+ - iterable but not threadsafe
33
+ - comparisons compare identity before equality, like builtins
34
+ - in boolean context, falsy when either empty or full,
35
+ otherwise truthy
36
+ - function ``caf`` produces fixed capacity circular array
37
+ from arguments
38
+
39
+ """
38
40
  __slots__ = '_xs', '_cnt', '_cap', '_front', '_rear'
39
41
 
40
42
  def __init__(self, *xs: Iterable[X], cap: int = 2) -> None:
41
43
  """
42
- :param xs: Optionally takes a single iterable to initially populate the circular array.
43
- :param cap: Minimum fixed storage capacity of circular array.
44
- :raises TypeError: When ``xs[0]`` not iterable,
45
- :raises ValueError: If more than 1 iterable is given.
44
+ .. admonition:: initializer
45
+
46
+ Populate ``CAF`` with an optional iterable
47
+ from front (left) to rear (right).
48
+
49
+ :param xs: Takes 0 or 1 iterable parameters.
50
+ :raises ValueError: When more than one parameter is provided.
51
+ :raises TypeError: When passed a non-iterable parameter.
46
52
 
47
53
  """
48
54
  cap = max(2, cap)
49
55
  if (size := len(xs)) > 1:
50
- msg = f'CAF expects at most 1 argument, got {size}'
56
+ msg = f'CAF expects at most 1 iterable, got {size}'
51
57
  raise ValueError(msg)
52
58
  if size:
53
59
  values: list[X | NoValue] = list(cast(Iterable[X | NoValue], xs[0]))
@@ -66,7 +72,96 @@ class CAF[X]:
66
72
  self._front = 0
67
73
  self._rear = cnt - 1
68
74
 
75
+ def __bool__(self) -> bool:
76
+ """
77
+ .. admonition:: bool
78
+
79
+ - falsy if either empty or full
80
+ - truthy otherwise
81
+
82
+ :returns: ``True`` when partially filled,
83
+ ``False`` otherwise.
84
+
85
+ """
86
+ return 0 < self._cnt < self._cap
87
+
88
+ def __len__(self) -> int:
89
+ """
90
+ .. admonition:: length
91
+
92
+ Number of items in the ``CAF``.
93
+
94
+ :returns: The number of items in the ``CAF``.
95
+
96
+ """
97
+ return self._cnt
98
+
99
+ def __eq__(self, other: object) -> bool:
100
+ """
101
+ .. admonition:: equality comparison
102
+
103
+ Efficiently compare ``CAF`` to another object.
104
+
105
+ :param other: The object to be compared.
106
+ :returns: ``True`` if ``other`` is another ``CAF`` whose
107
+ contents compare as equal to the corresponding
108
+ contents of the ``CAF``, otherwise ``False``.
109
+
110
+ """
111
+ if self is other:
112
+ return True
113
+ if not isinstance(other, type(self)):
114
+ return False
115
+
116
+ (
117
+ front1,
118
+ cnt1,
119
+ cap1,
120
+ front2,
121
+ cnt2,
122
+ cap2,
123
+ ) = (
124
+ self._front,
125
+ self._cnt,
126
+ self._cap,
127
+ other._front,
128
+ other._cnt,
129
+ other._cap,
130
+ )
131
+
132
+ if cnt1 != cnt2:
133
+ return False
134
+
135
+ for nn in range(cnt1):
136
+ if (
137
+ self._xs[(front1 + nn) % cap1]
138
+ is other._xs[(front2 + nn) % cap2]
139
+ ):
140
+ continue
141
+ if (
142
+ self._xs[(front1 + nn) % cap1]
143
+ != other._xs[(front2 + nn) % cap2]
144
+ ):
145
+ return False
146
+ return True
147
+
69
148
  def __iter__(self) -> Iterator[X]:
149
+ """
150
+ .. admonition:: iterate
151
+
152
+ Iterates circular array, front (left) to rear (right).
153
+
154
+ .. warning
155
+
156
+ Not thread safe, especially for long living iterators.
157
+
158
+ .. tip::
159
+
160
+ Cache contents to make more thread tolerant. Put
161
+ a lock around circular array during caching process
162
+ to make threadsafe.
163
+
164
+ """
70
165
  if self._cnt > 0:
71
166
  (
72
167
  cap,
@@ -86,6 +181,22 @@ class CAF[X]:
86
181
  yield cast(X, current_state[position])
87
182
 
88
183
  def __reversed__(self) -> Iterator[X]:
184
+ """
185
+ .. admonition:: reverse iterate
186
+
187
+ Iterates circular array, rear (right) to front (left).
188
+
189
+ .. warning
190
+
191
+ Not thread safe, especially for long living iterators.
192
+
193
+ .. tip::
194
+
195
+ Cache contents to make more thread tolerant. Put
196
+ a lock around circular array during caching process
197
+ to make threadsafe.
198
+
199
+ """
89
200
  if self._cnt > 0:
90
201
  (
91
202
  cap,
@@ -104,27 +215,14 @@ class CAF[X]:
104
215
  position = (position - 1) % cap
105
216
  yield cast(X, current_state[position])
106
217
 
107
- def __repr__(self) -> str:
218
+ def __getitem__(self, idx: int) -> X:
108
219
  """
109
- :returns: String of the form ``caf(x1, x2, ..., xn)``.
220
+ .. admonition:: getitem
110
221
 
111
- """
112
- return 'caf(' + ', '.join(map(repr, self)) + ')'
222
+ Fixed capacity circular arrays are indexable but
223
+ not sliceable.
113
224
 
114
- def __str__(self) -> str:
115
225
  """
116
- :returns: String of the form ``(|x1, x2, ..., xn|)``.
117
-
118
- """
119
- return '(|' + ', '.join(map(str, self)) + '|)'
120
-
121
- def __bool__(self) -> bool:
122
- return 0 < self._cnt < self._cap
123
-
124
- def __len__(self) -> int:
125
- return self._cnt
126
-
127
- def __getitem__(self, idx: int) -> X:
128
226
  cnt = self._cnt
129
227
  if 0 <= idx < cnt:
130
228
  return cast(X, self._xs[(self._front + idx) % self._cap])
@@ -142,6 +240,13 @@ class CAF[X]:
142
240
  raise IndexError(msg1 + msg2 + msg3)
143
241
 
144
242
  def __setitem__(self, idx: int, val: X) -> None:
243
+ """
244
+ .. admonition:: setitem
245
+
246
+ Fixed capacity circular arrays are indexable but
247
+ not sliceable.
248
+
249
+ """
145
250
  cnt = self._cnt
146
251
  if 0 <= idx < cnt:
147
252
  self._xs[(self._front + idx) % self._cap] = val
@@ -157,6 +262,13 @@ class CAF[X]:
157
262
  raise IndexError(msg1 + msg2 + msg3)
158
263
 
159
264
  def __delitem__(self, idx: int) -> None:
265
+ """
266
+ .. admonition:: delitem
267
+
268
+ Fixed capacity circular arrays are indexable but
269
+ not sliceable.
270
+
271
+ """
160
272
  item_list = list(self)
161
273
  del item_list[idx]
162
274
  _ca = CAF(item_list, cap = self._cap)
@@ -173,59 +285,39 @@ class CAF[X]:
173
285
  )
174
286
  del _ca
175
287
 
176
- def __eq__(self, other: object) -> bool:
288
+ def __repr__(self) -> str:
177
289
  """
178
- :param other: The object to be compared to.
179
- :returns: ``True`` if object is another ``CAF`` whose items compare
180
- as equal to the corresponding items in the ``CAF``,
181
- otherwise ``False``.
290
+ .. admonition:: repr string
291
+
292
+ Construct string 'CAF(x₁, x₂, … xₙ)' where
293
+ x₁, x₂, … xₙ are the contents displayed with ``repr()``.
294
+
295
+ :returns: A string to reproduce the ``CAF``.
182
296
 
183
297
  """
184
- if self is other:
185
- return True
186
- if not isinstance(other, type(self)):
187
- return False
298
+ return 'caf(' + ', '.join(map(repr, self)) + ')'
188
299
 
189
- (
190
- front1,
191
- cnt1,
192
- cap1,
193
- front2,
194
- cnt2,
195
- cap2,
196
- ) = (
197
- self._front,
198
- self._cnt,
199
- self._cap,
200
- other._front,
201
- other._cnt,
202
- other._cap,
203
- )
300
+ def __str__(self) -> str:
301
+ r"""
302
+ .. admonition:: user string
303
+ Construct string '(\|x₁, x₂, … xₙ\|)' where
304
+ x₁, x₂, ..., xₙ are the contents displayed with ``str()``.
204
305
 
205
- if cnt1 != cnt2:
206
- return False
306
+ :returns: A string meaningful to an end user.
207
307
 
208
- for nn in range(cnt1):
209
- if (
210
- self._xs[(front1 + nn) % cap1]
211
- is other._xs[(front2 + nn) % cap2]
212
- ):
213
- continue
214
- if (
215
- self._xs[(front1 + nn) % cap1]
216
- != other._xs[(front2 + nn) % cap2]
217
- ):
218
- return False
219
- return True
308
+ """
309
+ return '(|' + ', '.join(map(str, self)) + '|)'
220
310
 
221
- def pushl(self, item: X) -> None:
311
+ def pushl(self, x: X) -> None:
222
312
  """
223
- .. admonition:: Push left
313
+ .. admonition:: push left
224
314
 
225
315
  Push single item from the left onto the ``CAF``.
226
316
 
227
- :param x: Single item to be pushed onto the front of the circular array from the left.
228
- :raises ValueError: When called on a full fixed storage capacity circular array.
317
+ :param x: Single item to be pushed onto the front of
318
+ the ``CAF`` from the left.
319
+ :raises ValueError: When called on a full fixed storage
320
+ capacity circular array.
229
321
 
230
322
  """
231
323
  if self._cnt == self._cap:
@@ -238,18 +330,20 @@ class CAF[X]:
238
330
  self._cnt,
239
331
  ) = (
240
332
  (self._front - 1) % self._cap,
241
- item,
333
+ x,
242
334
  self._cnt + 1,
243
335
  )
244
336
 
245
- def pushr(self, item: X) -> None:
337
+ def pushr(self, x: X) -> None:
246
338
  """
247
- .. admonition:: Push right
339
+ .. admonition:: push right
248
340
 
249
341
  Push single item from the right onto the ``CAF``.
250
342
 
251
- :param x: Single item to be pushed onto the rear of the circular array from the right.
252
- :raises ValueError: When called on a full fixed storage capacity circular array.
343
+ :param x: Single item to be pushed onto the rear of
344
+ the ``CAF`` from the right.
345
+ :raises ValueError: When called on a full fixed
346
+ storage capacity circular array.
253
347
 
254
348
  """
255
349
  if self._cnt == self._cap:
@@ -262,23 +356,23 @@ class CAF[X]:
262
356
  self._cnt,
263
357
  ) = (
264
358
  (self._rear + 1) % self._cap,
265
- item,
359
+ x,
266
360
  self._cnt + 1,
267
361
  )
268
362
 
269
363
  def popl(self) -> X:
270
364
  """
271
- .. admonition:: Pop left
365
+ .. admonition:: pop left
272
366
 
273
367
  Pop a single items off the left side of the ``CAF``.
274
368
 
275
- :returns: Item popped from left side (front) of circular array.
276
- :raises ValueError: When called on an empty circular array.
369
+ :returns: Item popped from left side (front) of the ``CAF``.
370
+ :raises ValueError: When called on an empty ``CAF``.
277
371
 
278
372
  """
279
373
  if self._cnt > 1:
280
374
  (
281
- d,
375
+ x,
282
376
  self._xs[self._front],
283
377
  self._front,
284
378
  self._cnt,
@@ -290,7 +384,7 @@ class CAF[X]:
290
384
  )
291
385
  elif self._cnt == 1:
292
386
  (
293
- d,
387
+ x,
294
388
  self._xs[self._front],
295
389
  self._cnt,
296
390
  self._front,
@@ -305,21 +399,21 @@ class CAF[X]:
305
399
  else:
306
400
  msg = 'Method popl called on an empty CAF'
307
401
  raise ValueError(msg)
308
- return cast(X, d)
402
+ return cast(X, x)
309
403
 
310
404
  def popr(self) -> X:
311
405
  """
312
- .. admonition:: Pop right
406
+ .. admonition:: pop right
313
407
 
314
408
  Pop a single items off the right side of the ``CAF``.
315
409
 
316
- :returns: Item popped from right side (rear) of circular array.
317
- :raises ValueError: When called on an empty circular array.
410
+ :returns: Item popped from right side (rear) of the ``CAF``.
411
+ :raises ValueError: When called on an empty ``CAF``.
318
412
 
319
413
  """
320
414
  if self._cnt > 1:
321
415
  (
322
- d,
416
+ x,
323
417
  self._xs[self._rear],
324
418
  self._rear,
325
419
  self._cnt,
@@ -331,7 +425,7 @@ class CAF[X]:
331
425
  )
332
426
  elif self._cnt == 1:
333
427
  (
334
- d,
428
+ x,
335
429
  self._xs[self._front],
336
430
  self._cnt,
337
431
  self._front,
@@ -346,18 +440,18 @@ class CAF[X]:
346
440
  else:
347
441
  msg = 'Method popr called on an empty CAF'
348
442
  raise ValueError(msg)
349
- return cast(X, d)
443
+ return cast(X, x)
350
444
 
351
445
  def popld(self, default: X) -> X:
352
446
  """
353
- .. admonition:: Pop Left with default
447
+ .. admonition:: pop Left with default
354
448
 
355
449
  Pop a single items off the left side of the ``CAF``.
356
450
 
357
- :param default: Default value to return if ``CAF`` is empty.
358
- :returns: Item popped from left side (front) of circular array
359
- if not empty, otherwise return the provided default
360
- value.
451
+ :param default: Default value to return if ``CAF`` is empty.
452
+ :returns: Item popped from left side (front) of the ``CAF``
453
+ if not empty, otherwise return the provided
454
+ default value.
361
455
 
362
456
  """
363
457
  try:
@@ -367,14 +461,14 @@ class CAF[X]:
367
461
 
368
462
  def poprd(self, default: X) -> X:
369
463
  """
370
- .. admonition:: Pop Right with default
464
+ .. admonition:: pop Right with default
371
465
 
372
466
  Pop a single items off the right side of the ``CAF``.
373
467
 
374
- :param default: Default value to return if ``CAF`` is empty.
375
- :returns: Item popped from right side (rear) of circular array
376
- if not empty, otherwise return the provided default
377
- value.
468
+ :param default: Default value to return if ``CAF`` is empty.
469
+ :returns: Item popped from right side (rear) of the ``CAF``
470
+ if not empty, otherwise return the provided
471
+ default value.
378
472
 
379
473
  """
380
474
  try:
@@ -388,21 +482,22 @@ class CAF[X]:
388
482
 
389
483
  Pop items off the left side of the ``CAF``.
390
484
 
391
- :param maximum: Maximum number of items to pop, may pop less if not enough items in ``CAF``.
392
- :returns: A ``tuple`` of the items popped, left to right.
485
+ :param maximum: Maximum number of items to pop,
486
+ may pop less if not enough items in ``CAF``.
487
+ :returns: A ``tuple`` of the items popped, left to right.
393
488
 
394
489
  """
395
- item_list: list[X] = []
490
+ xs: list[X] = []
396
491
 
397
492
  while maximum > 0:
398
493
  try:
399
- item_list.append(self.popl())
494
+ xs.append(self.popl())
400
495
  except ValueError:
401
496
  break
402
497
  else:
403
498
  maximum -= 1
404
499
 
405
- return tuple(item_list)
500
+ return tuple(xs)
406
501
 
407
502
  def poprt(self, maximum: int) -> tuple[X, ...]:
408
503
  """
@@ -410,19 +505,20 @@ class CAF[X]:
410
505
 
411
506
  Pop items off the right side of the ``CAF``.
412
507
 
413
- :param maximum: Maximum number of items to pop, may pop less if not enough items in ``CAF``.
414
- :returns: A ``tuple`` of the items popped, right to left.
508
+ :param maximum: Maximum number of items to pop,
509
+ may pop less if not enough items in ``CAF``.
510
+ :returns: A ``tuple`` of the items popped, right to left.
415
511
 
416
512
  """
417
- item_list: list[X] = []
513
+ xs: list[X] = []
418
514
  while maximum > 0:
419
515
  try:
420
- item_list.append(self.popr())
516
+ xs.append(self.popr())
421
517
  except ValueError:
422
518
  break
423
519
  else:
424
520
  maximum -= 1
425
- return tuple(item_list)
521
+ return tuple(xs)
426
522
 
427
523
  def rotl(self, n: int = 1) -> None:
428
524
  """
@@ -431,7 +527,8 @@ class CAF[X]:
431
527
  Rotate contents of ``CAF`` to the left putting first
432
528
  item onto rear.
433
529
 
434
- :param n: Number of times to shift items left. Default 1 time.
530
+ :param n: Number of times to shift items left.
531
+ Default 1 time.
435
532
 
436
533
  """
437
534
  if self._cnt < 2:
@@ -446,7 +543,8 @@ class CAF[X]:
446
543
  Rotate contents of ``CAF`` to the right putting last
447
544
  item onto front.
448
545
 
449
- :param n: Number of times to shift items right. Default 1 time.
546
+ :param n: Number of times to shift items right.
547
+ Default 1 time.
450
548
 
451
549
  """
452
550
  if self._cnt < 2:
@@ -460,8 +558,8 @@ class CAF[X]:
460
558
 
461
559
  Apply function ``f`` over the circular array's contents.
462
560
 
463
- :param f: Callable from type ``X`` to type ``Y``.
464
- :returns: New fixed capacity circular array instance.
561
+ :param f: Callable from type ``X`` to type ``Y``.
562
+ :returns: New fixed capacity circular array instance.
465
563
 
466
564
  """
467
565
  return CAF(map(f, self), cap = self._cap)
@@ -477,10 +575,12 @@ class CAF[X]:
477
575
 
478
576
  Fold ``CAF`` left with a function and optional starting item.
479
577
 
480
- :param f: Folding function, first argument to ``f`` is for the accumulator.
481
- :param start: Optional starting item.
482
- :returns: Reduced value produced by the left fold.
483
- :raises ValueError: When circular array empty and ``start`` not given.
578
+ :param f: Folding function, first argument to ``f`` is for
579
+ the accumulator.
580
+ :param start: Optional starting item.
581
+ :returns: Reduced value produced by the left fold.
582
+ :raises ValueError: When circular array empty and ``start``
583
+ not given.
484
584
 
485
585
  """
486
586
  if self._cnt == 0:
@@ -496,8 +596,8 @@ class CAF[X]:
496
596
  return acc
497
597
 
498
598
  acc = cast(L, start)
499
- for d in self:
500
- acc = f(acc, d)
599
+ for x in self:
600
+ acc = f(acc, x)
501
601
  return acc
502
602
 
503
603
  @overload
@@ -511,10 +611,12 @@ class CAF[X]:
511
611
 
512
612
  Fold ``CAF`` right left with a function and optional starting item.
513
613
 
514
- :param f: Folding function, second argument to ``f`` is for the accumulator.
515
- :param start: Optional starting item.
516
- :returns: Reduced value produced by the right fold.
517
- :raises ValueError: When circular array empty and ``start`` not given.
614
+ :param f: Folding function, second argument to ``f`` is for
615
+ the accumulator.
616
+ :param start: Optional starting item.
617
+ :returns: Reduced value produced by the right fold.
618
+ :raises ValueError: When circular array empty and ``start``
619
+ not given.
518
620
 
519
621
  """
520
622
  if self._cnt == 0:
@@ -530,8 +632,8 @@ class CAF[X]:
530
632
  return acc
531
633
 
532
634
  acc = cast(R, start)
533
- for d in reversed(self):
534
- acc = f(d, acc)
635
+ for x in reversed(self):
636
+ acc = f(x, acc)
535
637
  return acc
536
638
 
537
639
  def capacity(self) -> int:
@@ -540,8 +642,7 @@ class CAF[X]:
540
642
 
541
643
  Get the fixed storage capacity of the circular array.
542
644
 
543
-
544
- :returns: Fixed storage capacity.
645
+ :returns: Fixed storage capacity.
545
646
 
546
647
  """
547
648
  return self._cap
@@ -571,7 +672,7 @@ class CAF[X]:
571
672
 
572
673
  Find fraction of the storage capacity which is filled.
573
674
 
574
- :returns: The ratio count/capacity.
675
+ :returns: The ratio count/capacity.
575
676
 
576
677
  """
577
678
  return self._cnt / self._cap
@@ -581,11 +682,12 @@ def caf[T](*ts: T, cap: int = 2) -> CAF[T]:
581
682
  """
582
683
  .. admonition:: Circular array factory function
583
684
 
584
- Produce a circular array from a variable number of arguments.
685
+ Produce a fixed capacity circular array from
686
+ a variable number of arguments.
585
687
 
586
- :param ts: Initial items for a new fixed capacity circular array.
587
- :param cap: The minimum storage capacity to set.
588
- :returns: New fixed storage capacity circular array.
688
+ :param ts: Initial items for a new fixed capacity circular array.
689
+ :param cap: The minimum storage capacity to set.
690
+ :returns: New fixed storage capacity circular array.
589
691
 
590
692
  """
591
693
  return CAF(ts, cap=cap)
@@ -5,16 +5,16 @@ __all__ = ['CAF', 'caf']
5
5
 
6
6
  class CAF[X]:
7
7
  def __init__(self, *xs: Iterable[X], cap: int = 2) -> None: ...
8
- def __iter__(self) -> Iterator[X]: ...
9
- def __reversed__(self) -> Iterator[X]: ...
10
8
  def __bool__(self) -> bool: ...
11
9
  def __len__(self) -> int: ...
10
+ def __eq__(self, other: object) -> bool: ...
11
+ def __iter__(self) -> Iterator[X]: ...
12
+ def __reversed__(self) -> Iterator[X]: ...
12
13
  def __getitem__(self, idx: int) -> X: ...
13
14
  def __setitem__(self, idx: int, val: X) -> None: ...
14
15
  def __delitem__(self, idx: int) -> None: ...
15
- def __eq__(self, other: object) -> bool: ...
16
- def pushl(self, item: X) -> None: ...
17
- def pushr(self, item: X) -> None: ...
16
+ def pushl(self, x: X) -> None: ...
17
+ def pushr(self, x: X) -> None: ...
18
18
  def popl(self) -> X: ...
19
19
  def popr(self) -> X: ...
20
20
  def popld(self, default: X) -> X: ...