pythonic-fp.iterables 4.0.0__py3-none-any.whl → 5.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.
pythonic_fp/iterables.py DELETED
@@ -1,653 +0,0 @@
1
- # Copyright 2023-2025 Geoffrey R. Scheller
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- """
16
- Library of functions for iterating iterables.
17
- ---------------------------------------------
18
-
19
- - Concatenating and merging iterables
20
- - Dropping and taking values from iterables
21
- - Reducing and accumulating iterables
22
- - Assumptions
23
-
24
- - iterables are not necessarily iterators
25
- - at all times iterator protocol is assumed to be followed
26
-
27
- - all iterators are assumed to be iterable
28
- - for all iterators ``foo`` we assume ``iter(foo) is foo``
29
-
30
- """
31
-
32
- from __future__ import annotations
33
-
34
- __author__ = 'Geoffrey R. Scheller'
35
- __copyright__ = 'Copyright (c) 2023-2025 Geoffrey R. Scheller'
36
- __license__ = 'Apache License 2.0'
37
-
38
- from collections.abc import Callable, Iterable, Iterator
39
- from enum import auto, Enum
40
- from typing import cast, Never
41
- from pythonic_fp.containers.box import Box
42
- from pythonic_fp.containers.maybe import MayBe
43
- from pythonic_fp.fptools.function import negate, swap
44
- from pythonic_fp.fptools.singletons import NoValue
45
-
46
- __all__ = [
47
- 'FM',
48
- 'concat',
49
- 'merge',
50
- 'exhaust',
51
- 'drop',
52
- 'drop_while',
53
- 'take',
54
- 'take_while',
55
- 'take_split',
56
- 'take_while_split',
57
- 'accumulate',
58
- 'reduce_left',
59
- 'fold_left',
60
- 'maybe_fold_left',
61
- 'sc_reduce_left',
62
- 'sc_reduce_right',
63
- ]
64
-
65
- # Iterate over multiple iterables
66
-
67
-
68
- class FM(Enum):
69
- """Iterable Blending Enums.
70
-
71
- - *CONCAT:* Concatenate iterables first to last
72
- - *MERGE:* Merge iterables until one is exhausted
73
- - *EXHAUST:* Merge iterables until all are exhausted
74
-
75
- """
76
- CONCAT = auto()
77
- MERGE = auto()
78
- EXHAUST = auto()
79
-
80
-
81
- def concat[D](*iterables: Iterable[D]) -> Iterator[D]:
82
- """Sequentially concatenate multiple iterables together.
83
-
84
- .. code:: python
85
-
86
- def concat[D](iterable: Iterable[D]) -> Iterator[D]
87
-
88
- - pure Python version of standard library's ``itertools.chain``
89
- - iterator sequentially yields each iterable until all are exhausted
90
- - an infinite iterable will prevent subsequent iterables from yielding any values
91
- - performant to ``itertools.chain``
92
-
93
- :param iterables: iterables to concatenate
94
- :return: concatenated iterators
95
-
96
- """
97
- for iterator in map(lambda x: iter(x), iterables):
98
- while True:
99
- try:
100
- value = next(iterator)
101
- yield value
102
- except StopIteration:
103
- break
104
-
105
-
106
- def merge[D](*iterables: Iterable[D], yield_partials: bool = False) -> Iterator[D]:
107
- """Shuffle together ``iterables`` until one is exhausted.
108
-
109
- .. code:: python
110
-
111
- def merge[D](iterable: Iterable[D], yield_partials: bool) -> Iterator[D]
112
-
113
- If ``yield_partials`` is true
114
-
115
- - yield any unmatched yielded values from other iterables
116
- - prevents data lose if any of the iterables are iterators with external references
117
-
118
- :param iterables: iterables to merge until one gets exhausted
119
- :return: merged iterators
120
-
121
- """
122
- iter_list = list(map(lambda x: iter(x), iterables))
123
- values = []
124
- if (num_iters := len(iter_list)) > 0:
125
- while True:
126
- try:
127
- for ii in range(num_iters):
128
- values.append(next(iter_list[ii]))
129
- yield from values
130
- values.clear()
131
- except StopIteration:
132
- break
133
- if yield_partials:
134
- yield from values
135
-
136
-
137
- def exhaust[D](*iterables: Iterable[D]) -> Iterator[D]:
138
- """Shuffle together multiple iterables until all are exhausted.
139
-
140
- .. code:: python
141
-
142
- def exhaust[D](iterable: Iterable[D]) -> Iterator[D]
143
-
144
- :param iterables: iterables to exhaustively merge
145
- :return: merged iterators
146
-
147
- """
148
- iter_list = list(map(lambda x: iter(x), iterables))
149
- if (num_iters := len(iter_list)) > 0:
150
- ii = 0
151
- values = []
152
- while True:
153
- try:
154
- while ii < num_iters:
155
- values.append(next(iter_list[ii]))
156
- ii += 1
157
- yield from values
158
- ii = 0
159
- values.clear()
160
- except StopIteration:
161
- num_iters -= 1
162
- if num_iters < 1:
163
- break
164
- del iter_list[ii]
165
-
166
- yield from values
167
-
168
-
169
- ## dropping and taking
170
-
171
-
172
- def drop[D](iterable: Iterable[D], n: int) -> Iterator[D]:
173
- """Drop the next n values from iterable.
174
-
175
- .. code:: python
176
-
177
- def drop[D](
178
- iterable: Iterable[D],
179
- n: int
180
- ) -> Iterator[D]
181
-
182
- :param iterable: iterable whose values are to be dropped
183
- :param n: number of values to be dropped
184
- :return: an iterator of the remaining values
185
-
186
- """
187
- it = iter(iterable)
188
- for _ in range(n):
189
- try:
190
- next(it)
191
- except StopIteration:
192
- break
193
- return it
194
-
195
-
196
- def drop_while[D](iterable: Iterable[D], pred: Callable[[D], bool]) -> Iterator[D]:
197
- """Drop initial values from iterable while predicate is true.
198
-
199
- .. code:: python
200
-
201
- def drop_while[D](
202
- iterable: Iterable[D],
203
- pred: Callable[[D], bool]
204
- ) -> Iterator[D]
205
-
206
- :param iterable: iterable whose values are to be dropped
207
- :param pred: Boolean valued function
208
- :return: an iterator beginning where pred returned false
209
-
210
- """
211
- it = iter(iterable)
212
- while True:
213
- try:
214
- value = next(it)
215
- if not pred(value):
216
- it = concat((value,), it)
217
- break
218
- except StopIteration:
219
- break
220
- return it
221
-
222
-
223
- def take[D](iterable: Iterable[D], n: int) -> Iterator[D]:
224
- """Return an iterator of up to n initial values of an iterable.
225
-
226
- .. code:: python
227
-
228
- def take[D](
229
- iterable: Iterable[D],
230
- n: int
231
- ) -> Iterator[D]
232
-
233
- :param iterable: iterable providing the values to be taken
234
- :param n: number of values to be dropped
235
- :return: an iterator of up to n initial values from iterable
236
-
237
- """
238
- it = iter(iterable)
239
- for _ in range(n):
240
- try:
241
- value = next(it)
242
- yield value
243
- except StopIteration:
244
- break
245
-
246
-
247
- def take_while[D](iterable: Iterable[D], pred: Callable[[D], bool]) -> Iterator[D]:
248
- """Yield values from iterable while predicate is true.
249
-
250
- .. code:: python
251
-
252
- def take_while[D](
253
- iterable: Iterable[D],
254
- pred: Callable[[D], bool]
255
- ) -> Iterator[D]
256
-
257
- .. warning::
258
- Risk of value loss if iterable is multiple referenced iterator.
259
-
260
- :param iterable: iterable providing the values to be taken
261
- :param pred: Boolean valued function
262
- :return: an Iterator of up to n initial values from the iterable
263
-
264
- """
265
- it = iter(iterable)
266
- while True:
267
- try:
268
- value = next(it)
269
- if pred(value):
270
- yield value
271
- else:
272
- break
273
- except StopIteration:
274
- break
275
-
276
-
277
- def take_split[D](iterable: Iterable[D], n: int) -> tuple[Iterator[D], Iterator[D]]:
278
- """Same as take except also return iterator of remaining values.
279
-
280
- .. code:: python
281
-
282
- def take_split[D](
283
- iterable: Iterable[D],
284
- n: int
285
- ) -> tuple[Iterator[D], Iterator[D]]
286
-
287
- .. Warning::
288
-
289
- **CONTRACT:** Do not access the second returned iterator until the
290
- first one is exhausted.
291
-
292
- :param iterable: iterable providing the values to be taken
293
- :param n: maximum number of values to be taken
294
- :return: an iterator of values taken and an iterator of remaining values
295
-
296
- """
297
- it = iter(iterable)
298
- itn = take(it, n)
299
-
300
- return itn, it
301
-
302
-
303
- def take_while_split[D](
304
- iterable: Iterable[D], pred: Callable[[D], bool]
305
- ) -> tuple[Iterator[D], Iterator[D]]:
306
- """Yield values from iterable while predicate is true.
307
-
308
- .. code:: python
309
-
310
- def take_while_split[D](
311
- iterable: Iterable[D],
312
- pred: Callable[[D], bool]
313
- ) -> tuple[Iterator[D], Iterator[D]]
314
-
315
- .. Warning::
316
-
317
- **CONTRACT:** Do not access the second returned iterator until
318
- the first one is exhausted.
319
-
320
- :param iterable: iterable providing the values to be taken
321
- :param pred: Boolean valued function
322
- :return: an iterator of values taken and an iterator of remaining values
323
-
324
- """
325
- def _take_while(
326
- it: Iterator[D], pred: Callable[[D], bool], val: Box[D]
327
- ) -> Iterator[D]:
328
- while True:
329
- try:
330
- val.put(next(it))
331
- if pred(val.get()):
332
- yield val.pop()
333
- else:
334
- break
335
- except StopIteration:
336
- break
337
-
338
- it = iter(iterable)
339
- value: Box[D] = Box()
340
- it_pred = _take_while(it, pred, value)
341
-
342
- return (it_pred, concat(value, it))
343
-
344
-
345
- ## reducing and accumulating
346
-
347
-
348
- def accumulate[D, L](
349
- iterable: Iterable[D],
350
- f: Callable[[L, D], L],
351
- initial: L | NoValue = NoValue()
352
- ) -> Iterator[L]:
353
- """Returns an iterator of partial fold values.
354
-
355
- .. code:: python
356
-
357
- def accumulate[D, L](
358
- iterable: Iterable[D],
359
- f: Callable[[L, D], L],
360
- initial: L
361
- ) -> Iterator[L]
362
-
363
- A pure Python version of standard library's ``itertools.accumulate``
364
-
365
- - function ``f`` does not default to addition (for typing flexibility)
366
- - begins accumulation with an "optional" ``initial`` value
367
-
368
- :param iterable: iterable to be folded
369
- :param f: 2 parameter function, first parameter for accumulated value
370
- :param initial: "optional" initial value to start fold
371
- :return: an iterator of the intermediate fold values
372
-
373
- """
374
- it = iter(iterable)
375
- try:
376
- it0 = next(it)
377
- except StopIteration:
378
- if initial is NoValue():
379
- return
380
- yield cast(L, initial)
381
- else:
382
- if initial is not NoValue():
383
- init = cast(L, initial)
384
- yield init
385
- acc = f(init, it0)
386
- for ii in it:
387
- yield acc
388
- acc = f(acc, ii)
389
- yield acc
390
- else:
391
- acc = cast(L, it0) # in this case L = D
392
- for ii in it:
393
- yield acc
394
- acc = f(acc, ii)
395
- yield acc
396
-
397
-
398
- def reduce_left[D](iterable: Iterable[D], f: Callable[[D, D], D]) -> D | Never:
399
- """Fold an iterable left with a function.
400
-
401
- .. code:: python
402
-
403
- def reduce_left[D](
404
- iterable: Iterable[D],
405
- f: Callable[[D, D], D]
406
- ) -> D | Never
407
-
408
- .. Warning::
409
-
410
- This function never return if given an infinite iterable.
411
-
412
- .. Warning::
413
-
414
- This function does not catch or re-raises exceptions from ``f``.
415
-
416
- :param iterable: iterable to be reduced (folded)
417
- :param f: 2 parameter function, first parameter for accumulated value
418
- :return: reduced value from the iterable
419
- :raises StopIteration: when called on an empty circular array
420
- :raises Exception: does not catch or re-raises exceptions from ``f``.
421
-
422
- """
423
- it = iter(iterable)
424
- try:
425
- acc = next(it)
426
- except StopIteration as exc:
427
- msg = 'Attempt to reduce an empty iterable?'
428
- raise StopIteration(msg) from exc
429
-
430
- for v in it:
431
- acc = f(acc, v)
432
-
433
- return acc
434
-
435
-
436
- def fold_left[D, L](
437
- iterable: Iterable[D],
438
- f: Callable[[L, D], L],
439
- initial: L
440
- ) -> L | Never:
441
- """Fold an iterable left with a function and initial value.
442
-
443
- .. code:: python
444
-
445
- def fold_left[D, L](
446
- iterable: Iterable[D],
447
- f: Callable[[L, D], L],
448
- initial: L
449
- ) -> L | Never
450
-
451
- - not restricted to ``__add__`` for the folding function
452
- - initial value required, does not default to ``0`` for initial value
453
- - handles non-numeric data just find
454
-
455
- .. Warning::
456
-
457
- This function never return if given an infinite iterable.
458
-
459
- .. Warning::
460
-
461
- This function does not catch any exceptions ``f`` may raise.
462
-
463
- :param iterable: iterable to be folded
464
- :param f: 2 parameter function, first parameter for accumulated value
465
- :param initial: mandatory initial value to start fold
466
- :return: the folded value
467
-
468
- """
469
- acc = initial
470
- for v in iterable:
471
- acc = f(acc, v)
472
- return acc
473
-
474
-
475
- def maybe_fold_left[D, L](
476
- iterable: Iterable[D],
477
- f: Callable[[L, D], L],
478
- initial: L | NoValue = NoValue()
479
- ) -> MayBe[L] | Never:
480
- """Folds an iterable left with an "optional" initial value..
481
-
482
- .. code:: python
483
-
484
- def maybe_fold_left[D, L](
485
- iterable: Iterable[D],
486
- f: Callable[[L, D], L],
487
- initial: L
488
- ) -> MayBe[L] | Never
489
-
490
- - traditional FP type order given for function ``f``
491
- - when an initial value is not given then ``L = D``
492
- - if iterable empty and no ``initial`` value given, return ``MayBe()``
493
-
494
- .. Warning::
495
-
496
- This function never return if given an infinite iterable.
497
-
498
- .. Warning::
499
-
500
- This function does not catch any exceptions ``f`` may raise.
501
-
502
- :param iterable: The iterable to be folded.
503
- :param f: First argument is for the accumulated value.
504
- :param initial: Mandatory initial value to start fold.
505
- :return: Folded value if it exists.
506
-
507
- """
508
- acc: L
509
- it = iter(iterable)
510
- if initial is NoValue():
511
- try:
512
- acc = cast(L, next(it)) # in this case L = D
513
- except StopIteration:
514
- return MayBe()
515
- else:
516
- acc = cast(L, initial)
517
-
518
- for v in it:
519
- try:
520
- acc = f(acc, v)
521
- except Exception:
522
- return MayBe()
523
-
524
- return MayBe(acc)
525
-
526
-
527
- def sc_reduce_left[D](
528
- iterable: Iterable[D],
529
- f: Callable[[D, D], D],
530
- start: Callable[[D], bool] = (lambda d: True),
531
- stop: Callable[[D], bool] = (lambda d: False),
532
- include_start: bool = True,
533
- include_stop: bool = True,
534
- ) -> tuple[MayBe[D], Iterator[D]]:
535
- """Short circuit version of a left reduce.
536
-
537
- .. code:: python
538
-
539
- def sc_reduce_left[D](
540
- iterable: Iterable[D],
541
- f: Callable[[D, D], D],
542
- start: Callable[[D], bool],
543
- stop: Callable[[D], bool],
544
- include_start: bool,
545
- include_stop: bool
546
- ) -> tuple[MayBe[D], Iterator[D]]
547
-
548
- Useful for infinite iterables.
549
-
550
- Behavior for default arguments will
551
-
552
- - left reduce finite iterable
553
- - start folding immediately
554
- - continue folding until end (of a possibly infinite iterable)
555
-
556
- :param iterable: iterable to be reduced from the left
557
- :param f: 2 parameter function, first parameter for accumulated value
558
- :param start: delay starting the fold until it returns true
559
- :param stop: prematurely stop the fold when it returns true
560
- :param include_start: if true, include fold starting value in fold
561
- :param include_stop: if true, include stopping value in fold
562
- :return: MayBe of the folded value and remaining iterables
563
-
564
- """
565
- it_start = drop_while(iterable, negate(start))
566
- if not include_start:
567
- try:
568
- next(it_start)
569
- except StopIteration:
570
- pass
571
- it_reduce, it_rest = take_while_split(it_start, negate(stop))
572
- mb_reduced = maybe_fold_left(it_reduce, f)
573
- if include_stop:
574
- if mb_reduced:
575
- try:
576
- last = next(it_rest)
577
- mb_reduced = MayBe(f(mb_reduced.get(), last))
578
- except StopIteration:
579
- pass
580
- else:
581
- try:
582
- last = next(it_rest)
583
- mb_reduced = MayBe(last)
584
- except StopIteration:
585
- pass
586
-
587
- return (mb_reduced, it_rest)
588
-
589
-
590
- def sc_reduce_right[D](
591
- iterable: Iterable[D],
592
- f: Callable[[D, D], D],
593
- start: Callable[[D], bool] = (lambda d: False),
594
- stop: Callable[[D], bool] = (lambda d: False),
595
- include_start: bool = True,
596
- include_stop: bool = True,
597
- ) -> tuple[MayBe[D], Iterator[D]]:
598
- """Short circuit version of a right reduce.
599
-
600
- .. code:: python
601
-
602
- def sc_reduce_left[D](
603
- iterable: Iterable[D],
604
- f: Callable[[D, D], D],
605
- start: Callable[[D], bool],
606
- stop: Callable[[D], bool],
607
- include_start: bool,
608
- include_stop: bool
609
- ) -> Tuple[MayBe[D] | Iterator[D]
610
-
611
- Useful for infinite and non-reversible iterables.
612
-
613
- Behavior for default arguments will
614
-
615
- - right reduce finite iterable
616
- - start folding at end (of a possibly infinite iterable)
617
- - continue reducing right until beginning
618
-
619
- :param iterable: iterable to be reduced from the right
620
- :param f: 2 parameter function, second parameter for accumulated value
621
- :param start: delay starting the fold until it returns true
622
- :param stop: prematurely stop the fold when it returns true
623
- :param include_start: if true, include fold starting value in fold
624
- :param include_stop: if true, include stopping value in fold
625
- :return: MayBe of the folded value and remaining iterables
626
-
627
- """
628
- it_start, it_rest = take_while_split(iterable, negate(start))
629
- list1 = list(it_start)
630
- if include_start:
631
- try:
632
- begin = next(it_rest)
633
- except StopIteration:
634
- pass
635
- else:
636
- list1.append(begin)
637
-
638
- list1.reverse()
639
- it_reduce, it_stop = take_while_split(list1, negate(stop))
640
-
641
- mb_reduced = maybe_fold_left(it_reduce, swap(f))
642
- if include_stop:
643
- try:
644
- end = next(it_stop)
645
- except StopIteration:
646
- pass
647
- else:
648
- if mb_reduced:
649
- mb_reduced = MayBe(f(end, mb_reduced.get()))
650
- else:
651
- mb_reduced = MayBe(end)
652
-
653
- return (mb_reduced, it_rest)
@@ -1,5 +0,0 @@
1
- pythonic_fp/iterables.py,sha256=PMAE7J3GWFtOqVmIlquPprP584wxMnblO3AJxaI3WAQ,18186
2
- pythonic_fp_iterables-4.0.0.dist-info/licenses/LICENSE,sha256=IJMkVWQUVy-SonK6SMaMotDQ6Zb-lVi2bUTiGp1GUO8,10774
3
- pythonic_fp_iterables-4.0.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
4
- pythonic_fp_iterables-4.0.0.dist-info/METADATA,sha256=vOvNgxdVrs0JBSf3dSA8NaHZXiW_LEVNZ8LR51r7fUQ,2160
5
- pythonic_fp_iterables-4.0.0.dist-info/RECORD,,