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.
@@ -14,19 +14,17 @@
14
14
 
15
15
  """
16
16
  Circular Array
17
- --------------
17
+ ==============
18
18
 
19
- .. admonition:: Stateful circular array data structures.
19
+ .. admonition:: Stateful circular array data structures
20
20
 
21
- - O(1) pops and pushes either end
21
+ - O(1) pops and pushes on either side
22
22
  - O(1) size determination
23
23
  - O(1) indexing
24
- - Two types of circular arrays, fixed and variable capacity
24
+ - two types of circular arrays
25
25
 
26
- .. note::
27
-
28
- - Class constructors ``CA`` and ``CAF`` take an optional iterator to populate circular array.
29
- - Factory Functions ``ca`` and ``caf`` take a variable number of parameters to populate circular array.
26
+ - fixed capacity: ``CAF``
27
+ - variable capacity: ``CA``
30
28
 
31
29
  """
32
30
 
@@ -12,20 +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:: Variable storage capacity circular array CA
17
-
18
- - O(1) pops either end
19
- - O(1) amortized pushes either end
20
- - O(1) indexing, fully supports slicing
21
- - auto-resizing more storage capacity when necessary, manually compatible
22
- - iterable, safely mutates while iterators iterating over previous state
23
- - comparisons compare identity before equality, like builtins
24
- - in boolean context, falsy when empty, otherwise truthy
25
- - function ``ca`` produces auto-resizing circular array from arguments
26
-
27
- """
28
-
29
15
  from collections.abc import Callable, Iterable, Iterator
30
16
  from typing import cast, Final, overload
31
17
  from pythonic_fp.gadgets.sentinels.novalue import NoValue
@@ -36,17 +22,37 @@ nada: Final[NoValue] = NoValue()
36
22
 
37
23
 
38
24
  class CA[X]:
25
+ """
26
+ .. admonition:: Auto resizing circular array CA
27
+
28
+ - O(1) pops either end
29
+ - O(1) amortized pushes either end
30
+ - O(1) indexing, fully supports slicing
31
+ - auto-resizing more storage capacity when necessary,
32
+ manually compatible
33
+ - iterable but not threadsafe
34
+ - comparisons compare identity before equality, like builtins
35
+ - in boolean context, falsy when empty, otherwise truthy
36
+ - function ``ca`` produces auto-resizing circular array
37
+ from arguments
38
+
39
+ """
39
40
  __slots__ = '_xs', '_cnt', '_cap', '_front', '_rear'
40
41
 
41
42
  def __init__(self, *xs: Iterable[X]) -> None:
42
43
  """
43
- :param xs: Optionally takes a single iterable to initially populate the 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 ``CA`` 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
  if (size := len(xs)) > 1:
49
- msg = f'CA expects at most 1 argument, got {size}'
55
+ msg = f'CA expects at most 1 iterable, got {size}'
50
56
  raise ValueError(msg)
51
57
  if size:
52
58
  values: list[X | NoValue] = list(cast(Iterable[X | NoValue], xs[0]))
@@ -139,7 +145,96 @@ class CA[X]:
139
145
  + [nada],
140
146
  )
141
147
 
148
+ def __bool__(self) -> bool:
149
+ """
150
+ .. admonition:: bool
151
+
152
+ - falsy when empty
153
+ - truthy when not empty
154
+
155
+ :returns: ``True`` when not empty,
156
+ ``False`` otherwise.
157
+
158
+ """
159
+ return self._cnt > 0
160
+
161
+ def __len__(self) -> int:
162
+ """
163
+ .. admonition:: length
164
+
165
+ Number of items in the ``CA``.
166
+
167
+ :returns: The number of items in the ``CA``.
168
+
169
+ """
170
+ return self._cnt
171
+
172
+ def __eq__(self, other: object) -> bool:
173
+ """
174
+ .. admonition:: equality comparison
175
+
176
+ Efficiently compare ``CA`` to another object.
177
+
178
+ :param other: The object to be compared.
179
+ :returns: ``True`` if ``other`` is another ``CA`` whose
180
+ contents compare as equal to the corresponding
181
+ contents of the ``CA``, otherwise ``False``.
182
+
183
+ """
184
+ if self is other:
185
+ return True
186
+ if not isinstance(other, type(self)):
187
+ return False
188
+
189
+ (
190
+ front1,
191
+ cnt1,
192
+ capacity1,
193
+ front2,
194
+ cnt2,
195
+ capacity2,
196
+ ) = (
197
+ self._front,
198
+ self._cnt,
199
+ self._cap,
200
+ other._front,
201
+ other._cnt,
202
+ other._cap,
203
+ )
204
+
205
+ if cnt1 != cnt2:
206
+ return False
207
+
208
+ for nn in range(cnt1):
209
+ if (
210
+ self._xs[(front1 + nn) % capacity1]
211
+ is other._xs[(front2 + nn) % capacity2]
212
+ ):
213
+ continue
214
+ if (
215
+ self._xs[(front1 + nn) % capacity1]
216
+ != other._xs[(front2 + nn) % capacity2]
217
+ ):
218
+ return False
219
+ return True
220
+
142
221
  def __iter__(self) -> Iterator[X]:
222
+ """
223
+ .. admonition:: iterate
224
+
225
+ Iterates circular array, front (left) to rear (right).
226
+
227
+ .. warning
228
+
229
+ Not thread safe, especially for long living iterators.
230
+
231
+ .. tip::
232
+
233
+ Cache contents to make more thread tolerant. Put
234
+ a lock around circular array during caching process
235
+ to make threadsafe.
236
+
237
+ """
143
238
  if self._cnt > 0:
144
239
  (
145
240
  capacity,
@@ -159,6 +254,22 @@ class CA[X]:
159
254
  yield cast(X, current_state[position])
160
255
 
161
256
  def __reversed__(self) -> Iterator[X]:
257
+ """
258
+ .. admonition:: reverse iterate
259
+
260
+ Iterates circular array, rear (right) to front (left).
261
+
262
+ .. warning
263
+
264
+ Not thread safe, especially for long living iterators.
265
+
266
+ .. tip::
267
+
268
+ Cache contents to make more thread tolerant. Put
269
+ a lock around circular array during caching process
270
+ to make threadsafe.
271
+
272
+ """
162
273
  if self._cnt > 0:
163
274
  (
164
275
  capacity,
@@ -177,32 +288,19 @@ class CA[X]:
177
288
  position = (position - 1) % capacity
178
289
  yield cast(X, current_state[position])
179
290
 
180
- def __repr__(self) -> str:
181
- """
182
- :returns: String of the form ``ca(x1, x2, ..., xn)``.
183
-
184
- """
185
- return 'ca(' + ', '.join(map(repr, self)) + ')'
186
-
187
- def __str__(self) -> str:
188
- """
189
- :returns: String of the form ``(|x1, x2, ..., xn|)``.
190
-
191
- """
192
- return '(|' + ', '.join(map(str, self)) + '|)'
193
-
194
- def __bool__(self) -> bool:
195
- return self._cnt > 0
196
-
197
- def __len__(self) -> int:
198
- return self._cnt
199
-
200
291
  @overload
201
292
  def __getitem__(self, idx: int) -> X: ...
202
293
  @overload
203
294
  def __getitem__(self, idx: slice) -> 'CA[X]': ...
204
295
 
205
296
  def __getitem__(self, idx: int | slice) -> X | 'CA[X]':
297
+ """
298
+ .. admonition:: getitem
299
+
300
+ Auto resizing circular arrays are fully indexable
301
+ and sliceable.
302
+
303
+ """
206
304
  if isinstance(idx, slice):
207
305
  return CA(list(self)[idx])
208
306
 
@@ -228,6 +326,13 @@ class CA[X]:
228
326
  def __setitem__(self, idx: slice, vals: Iterable[X]) -> None: ...
229
327
 
230
328
  def __setitem__(self, idx: int | slice, vals: X | Iterable[X]) -> None:
329
+ """
330
+ .. admonition:: setitem
331
+
332
+ Auto resizing circular arrays are fully indexable
333
+ and sliceable.
334
+
335
+ """
231
336
  if isinstance(idx, slice):
232
337
  if isinstance(vals, Iterable):
233
338
  item_list = list(self)
@@ -271,6 +376,13 @@ class CA[X]:
271
376
  def __delitem__(self, idx: slice) -> None: ...
272
377
 
273
378
  def __delitem__(self, idx: int | slice) -> None:
379
+ """
380
+ .. admonition:: delitem
381
+
382
+ Auto resizing circular arrays are fully indexable
383
+ and sliceable.
384
+
385
+ """
274
386
  item_list = list(self)
275
387
  del item_list[idx]
276
388
  _ca = CA(item_list)
@@ -289,62 +401,42 @@ class CA[X]:
289
401
  )
290
402
  del _ca
291
403
 
292
- def __eq__(self, other: object) -> bool:
404
+ def __repr__(self) -> str:
293
405
  """
294
- :param other: The object to be compared to.
295
- :returns: ``True`` if object is another ``CA`` whose items compare
296
- as equal to the corresponding items in the ``CA``,
297
- otherwise ``False``.
406
+ .. admonition:: repr string
407
+
408
+ Construct string 'CA(x₁, x₂, … xₙ)' where
409
+ x₁, x₂, … xₙ are the contents displayed with ``repr()``.
410
+
411
+ :returns: A string to reproduce the ``CA``.
298
412
 
299
413
  """
300
- if self is other:
301
- return True
302
- if not isinstance(other, type(self)):
303
- return False
414
+ return 'ca(' + ', '.join(map(repr, self)) + ')'
304
415
 
305
- (
306
- front1,
307
- cnt1,
308
- capacity1,
309
- front2,
310
- cnt2,
311
- capacity2,
312
- ) = (
313
- self._front,
314
- self._cnt,
315
- self._cap,
316
- other._front,
317
- other._cnt,
318
- other._cap,
319
- )
416
+ def __str__(self) -> str:
417
+ r"""
418
+ .. admonition:: user string
320
419
 
321
- if cnt1 != cnt2:
322
- return False
420
+ Construct string '(\| x₁, x₂, … xₙ \|)' where
421
+ x₁, x₂, ..., xₙ are the contents displayed with ``str()``.
323
422
 
324
- for nn in range(cnt1):
325
- if (
326
- self._xs[(front1 + nn) % capacity1]
327
- is other._xs[(front2 + nn) % capacity2]
328
- ):
329
- continue
330
- if (
331
- self._xs[(front1 + nn) % capacity1]
332
- != other._xs[(front2 + nn) % capacity2]
333
- ):
334
- return False
335
- return True
423
+ :returns: A estring meaningful to an end user.
424
+
425
+ """
426
+ return '(| ' + ', '.join(map(str, self)) + ' |)'
336
427
 
337
428
  def pushl(self, *xs: X) -> None:
338
429
  """
339
- .. admonition:: Push left
430
+ .. admonition:: push left
340
431
 
341
432
  Push items from the left onto the ``CA`` in the
342
433
  order they were iterated.
343
434
 
344
- :param xs: Items to be pushed onto the front of the circular array from the left.
435
+ :param xs: Items to be pushed onto the front of the ``CA``
436
+ from the left.
345
437
 
346
438
  """
347
- for item in xs:
439
+ for x in xs:
348
440
  if self._cnt == self._cap:
349
441
  self._double_storage_capacity()
350
442
  (
@@ -353,18 +445,19 @@ class CA[X]:
353
445
  self._cnt,
354
446
  ) = (
355
447
  (self._front - 1) % self._cap,
356
- item,
448
+ x,
357
449
  self._cnt + 1,
358
450
  )
359
451
 
360
452
  def pushr(self, *xs: X) -> None:
361
453
  """
362
- .. admonition:: Push right
454
+ .. admonition:: push right
363
455
 
364
456
  Push items from the right onto the ``CA`` in the
365
457
  order they were iterated.
366
458
 
367
- :param xs: Items to be pushed onto the rear of the ``CA`` from the right.
459
+ :param xs: Items to be pushed onto the rear of
460
+ the ``CA`` from the right.
368
461
 
369
462
  """
370
463
  for item in xs:
@@ -382,17 +475,17 @@ class CA[X]:
382
475
 
383
476
  def popl(self) -> X:
384
477
  """
385
- .. admonition:: Pop left
478
+ .. admonition:: pop left
386
479
 
387
480
  Pop a single items off the left side of the ``CA``.
388
481
 
389
- :returns: Item popped from left side (front) of circular array.
390
- :raises ValueError: When called on an empty circular array.
482
+ :returns: Item popped from left side (front) of the ``CA``.
483
+ :raises ValueError: When called on an empty ``CA``.
391
484
 
392
485
  """
393
486
  if self._cnt > 1:
394
487
  (
395
- d,
488
+ x,
396
489
  self._xs[self._front],
397
490
  self._front,
398
491
  self._cnt,
@@ -404,7 +497,7 @@ class CA[X]:
404
497
  )
405
498
  elif self._cnt == 1:
406
499
  (
407
- d,
500
+ x,
408
501
  self._xs[self._front],
409
502
  self._cnt,
410
503
  self._front,
@@ -419,21 +512,21 @@ class CA[X]:
419
512
  else:
420
513
  msg = 'Method popl called on an empty CA'
421
514
  raise ValueError(msg)
422
- return cast(X, d)
515
+ return cast(X, x)
423
516
 
424
517
  def popr(self) -> X:
425
518
  """
426
- .. admonition:: Pop right
519
+ .. admonition:: pop right
427
520
 
428
521
  Pop a single items off the right side of the ``CA``.
429
522
 
430
- :returns: Item popped from right side (rear) of circular array.
431
- :raises ValueError: When called on an empty circular array.
523
+ :returns: Item popped from right side (rear) of the ``CA``.
524
+ :raises ValueError: When called on an empty ``CA``.
432
525
 
433
526
  """
434
527
  if self._cnt > 1:
435
528
  (
436
- d,
529
+ x,
437
530
  self._xs[self._rear],
438
531
  self._rear,
439
532
  self._cnt,
@@ -445,7 +538,7 @@ class CA[X]:
445
538
  )
446
539
  elif self._cnt == 1:
447
540
  (
448
- d,
541
+ x,
449
542
  self._xs[self._front],
450
543
  self._cnt,
451
544
  self._front,
@@ -460,18 +553,18 @@ class CA[X]:
460
553
  else:
461
554
  msg = 'Method popr called on an empty CA'
462
555
  raise ValueError(msg)
463
- return cast(X, d)
556
+ return cast(X, x)
464
557
 
465
558
  def popld(self, default: X) -> X:
466
559
  """
467
- .. admonition:: Pop Left with default
560
+ .. admonition:: pop Left with default
468
561
 
469
562
  Pop a single items off the left side of the ``CA``.
470
563
 
471
- :param default: Default value to return if ``CA`` is empty.
472
- :returns: Item popped from left side (front) of circular array
473
- if not empty, otherwise return the provided default
474
- value.
564
+ :param default: Default value to return if ``CA`` is empty.
565
+ :returns: Item popped from left side (front) of the ``CA``
566
+ if not empty, otherwise return the provided
567
+ default value.
475
568
 
476
569
  """
477
570
  try:
@@ -481,14 +574,14 @@ class CA[X]:
481
574
 
482
575
  def poprd(self, default: X) -> X:
483
576
  """
484
- .. admonition:: Pop Right with default
577
+ .. admonition:: pop Right with default
485
578
 
486
579
  Pop a single items off the right side of the ``CA``.
487
580
 
488
- :param default: Default value to return if ``CA`` is empty.
489
- :returns: Item popped from right side (rear) of circular array
490
- if not empty, otherwise return the provided default
491
- value.
581
+ :param default: Default value to return if ``CA`` is empty.
582
+ :returns: Item popped from right side (rear) of the ``CA``
583
+ if not empty, otherwise return the provided
584
+ default value.
492
585
 
493
586
  """
494
587
  try:
@@ -502,21 +595,22 @@ class CA[X]:
502
595
 
503
596
  Pop items off the left side of the ``CA``.
504
597
 
505
- :param maximum: Maximum number of items to pop, may pop less if not enough items in ``CA``.
506
- :returns: A ``tuple`` of the items popped, left to right.
598
+ :param maximum: Maximum number of items to pop,
599
+ may pop less if not enough items in ``CA``.
600
+ :returns: A ``tuple`` of the items popped, left to right.
507
601
 
508
602
  """
509
- item_list: list[X] = []
603
+ xs: list[X] = []
510
604
 
511
605
  while maximum > 0:
512
606
  try:
513
- item_list.append(self.popl())
607
+ xs.append(self.popl())
514
608
  except ValueError:
515
609
  break
516
610
  else:
517
611
  maximum -= 1
518
612
 
519
- return tuple(item_list)
613
+ return tuple(xs)
520
614
 
521
615
  def poprt(self, maximum: int) -> tuple[X, ...]:
522
616
  """
@@ -524,8 +618,9 @@ class CA[X]:
524
618
 
525
619
  Pop items off the right side of the ``CA``.
526
620
 
527
- :param maximum: Maximum number of items to pop, may pop less if not enough items in ``CA``.
528
- :returns: A ``tuple`` of the items popped, right to left.
621
+ :param maximum: Maximum number of items to pop,
622
+ may pop less if not enough items in ``CA``.
623
+ :returns: A ``tuple`` of the items popped, right to left.
529
624
 
530
625
  """
531
626
  item_list: list[X] = []
@@ -545,7 +640,8 @@ class CA[X]:
545
640
  Rotate contents of ``CA`` to the left putting first
546
641
  item onto rear.
547
642
 
548
- :param n: Number of times to shift items left. Default 1 time.
643
+ :param n: Number of times to shift items left.
644
+ Default 1 time.
549
645
 
550
646
  """
551
647
  if self._cnt < 2:
@@ -560,7 +656,8 @@ class CA[X]:
560
656
  Rotate contents of ``CA`` to the right putting last
561
657
  item onto front.
562
658
 
563
- :param n: Number of times to shift items right. Default 1 time.
659
+ :param n: Number of times to shift items right.
660
+ Default 1 time.
564
661
 
565
662
  """
566
663
  if self._cnt < 2:
@@ -574,8 +671,8 @@ class CA[X]:
574
671
 
575
672
  Apply function ``f`` over the circular array's contents.
576
673
 
577
- :param f: Callable from type ``X`` to type ``Y``.
578
- :returns: New auto-resizing circular array instance.
674
+ :param f: Callable from type ``X`` to type ``Y``.
675
+ :returns: New auto-resizing circular array instance.
579
676
 
580
677
  """
581
678
  return CA(map(f, self))
@@ -591,10 +688,12 @@ class CA[X]:
591
688
 
592
689
  Fold ``CA`` left with a function and optional starting item.
593
690
 
594
- :param f: Folding function, first argument to ``f`` is for the accumulator.
595
- :param start: Optional starting item.
596
- :returns: Reduced value produced by the left fold.
597
- :raises ValueError: When circular array empty and ``start`` not given.
691
+ :param f: Folding function, first argument to ``f`` is for
692
+ the accumulator.
693
+ :param start: Optional starting item.
694
+ :returns: Reduced value produced by the left fold.
695
+ :raises ValueError: When circular array empty and ``start``
696
+ not given.
598
697
 
599
698
  """
600
699
  if self._cnt == 0:
@@ -610,8 +709,8 @@ class CA[X]:
610
709
  return acc
611
710
 
612
711
  acc = cast(L, start)
613
- for d in self:
614
- acc = f(acc, d)
712
+ for x in self:
713
+ acc = f(acc, x)
615
714
  return acc
616
715
 
617
716
  @overload
@@ -625,10 +724,12 @@ class CA[X]:
625
724
 
626
725
  Fold ``CA`` right with a function and optional starting item.
627
726
 
628
- :param f: Folding function, second argument to ``f`` is for the accumulator.
629
- :param start: Optional starting item.
630
- :returns: Reduced value produced by the right fold.
631
- :raises ValueError: When circular array empty and ``start`` not given.
727
+ :param f: Folding function, second argument to ``f`` is for
728
+ the accumulator.
729
+ :param start: Optional starting item.
730
+ :returns: Reduced value produced by the right fold.
731
+ :raises ValueError: When circular array empty and ``start``
732
+ not given.
632
733
 
633
734
  """
634
735
  if self._cnt == 0:
@@ -644,8 +745,8 @@ class CA[X]:
644
745
  return acc
645
746
 
646
747
  acc = cast(R, start)
647
- for d in reversed(self):
648
- acc = f(d, acc)
748
+ for x in reversed(self):
749
+ acc = f(x, acc)
649
750
  return acc
650
751
 
651
752
  def capacity(self) -> int:
@@ -654,7 +755,7 @@ class CA[X]:
654
755
 
655
756
  Get the current storage capacity of the circular array.
656
757
 
657
- :returns: Current storage capacity.
758
+ :returns: Current storage capacity.
658
759
 
659
760
  """
660
761
  return self._cap
@@ -684,7 +785,7 @@ class CA[X]:
684
785
 
685
786
  Find fraction of the storage capacity which is filled.
686
787
 
687
- :returns: The ratio count/capacity.
788
+ :returns: The ratio count/capacity.
688
789
 
689
790
  """
690
791
  return self._cnt / self._cap
@@ -697,7 +798,8 @@ class CA[X]:
697
798
  minimum storage capacity. To just compact the circular
698
799
  array, do not provide ``minimum_capacity``.
699
800
 
700
- :param minimum_capacity: Minimum storage capacity to compact the circular array.
801
+ :param minimum_capacity: Minimum storage capacity to compact
802
+ the circular array.
701
803
 
702
804
  """
703
805
  self._compact_storage_capacity()
@@ -717,10 +819,11 @@ def ca[T](*ts: T) -> CA[T]:
717
819
  """
718
820
  .. admonition:: Circular array factory function
719
821
 
720
- Produce a circular array from a variable number of arguments.
822
+ Produce a auto resizing circular array from
823
+ a variable number of arguments.
721
824
 
722
- :param ts: Initial items for a new auto-resizing circular array.
723
- :returns: New variable storage capacity circular array.
825
+ :param ts: Initial items for a new auto-resizing circular array.
826
+ :returns: New variable storage capacity circular array.
724
827
 
725
828
  """
726
829
  return CA(ts)
@@ -5,10 +5,11 @@ __all__ = ['CA', 'ca']
5
5
 
6
6
  class CA[X]:
7
7
  def __init__(self, *xs: Iterable[X]) -> 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
  @overload
13
14
  def __getitem__(self, idx: int) -> X: ...
14
15
  @overload
@@ -21,7 +22,6 @@ class CA[X]:
21
22
  def __delitem__(self, idx: int) -> None: ...
22
23
  @overload
23
24
  def __delitem__(self, idx: slice) -> None: ...
24
- def __eq__(self, other: object) -> bool: ...
25
25
  def pushl(self, *xs: X) -> None: ...
26
26
  def pushr(self, *xs: X) -> None: ...
27
27
  def popl(self) -> X: ...