pythonic-fp.iterables 3.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/__init__.py +39 -0
- pythonic_fp/iterables/drop_take.py +177 -0
- pythonic_fp/iterables/folding.py +294 -0
- pythonic_fp/iterables/merging.py +148 -0
- pythonic_fp/iterables/py.typed +0 -0
- {pythonic_fp_iterables-3.0.0.dist-info → pythonic_fp_iterables-5.0.0.dist-info}/METADATA +28 -19
- pythonic_fp_iterables-5.0.0.dist-info/RECORD +9 -0
- pythonic_fp/iterables.py +0 -455
- pythonic_fp_iterables-3.0.0.dist-info/RECORD +0 -5
- {pythonic_fp_iterables-3.0.0.dist-info → pythonic_fp_iterables-5.0.0.dist-info}/WHEEL +0 -0
- {pythonic_fp_iterables-3.0.0.dist-info → pythonic_fp_iterables-5.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Copyright 2023-2024 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
|
+
"""Library of functions for iterating iterables.
|
|
16
|
+
|
|
17
|
+
+-----------+-------------------------------------------+
|
|
18
|
+
| Module | Description |
|
|
19
|
+
+===========+===========================================+
|
|
20
|
+
| merging | Concatenating and merging iterables |
|
|
21
|
+
+-----------+-------------------------------------------+
|
|
22
|
+
| drop_take | Dropping and taking values from iterables |
|
|
23
|
+
+-----------+-------------------------------------------+
|
|
24
|
+
| folding | Reducing and accumulating iterables |
|
|
25
|
+
+-----------+-------------------------------------------+
|
|
26
|
+
|
|
27
|
+
Assumptions:
|
|
28
|
+
|
|
29
|
+
- iterables are not necessarily iterators
|
|
30
|
+
- at all times iterator protocol is assumed to be followed
|
|
31
|
+
|
|
32
|
+
- all iterators are assumed to be iterable
|
|
33
|
+
- for all iterators ``foobar`` we assume ``iter(foobar) is foobar``
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
__author__ = 'Geoffrey R. Scheller'
|
|
38
|
+
__copyright__ = 'Copyright (c) 2023-2025 Geoffrey R. Scheller'
|
|
39
|
+
__license__ = 'Apache License 2.0'
|
|
@@ -0,0 +1,177 @@
|
|
|
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
|
+
"""Module pythonic_fp.iterables.drop_take"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
__author__ = 'Geoffrey R. Scheller'
|
|
20
|
+
__copyright__ = 'Copyright (c) 2023-2025 Geoffrey R. Scheller'
|
|
21
|
+
__license__ = 'Apache License 2.0'
|
|
22
|
+
|
|
23
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
24
|
+
from pythonic_fp.gadgets.box import Box
|
|
25
|
+
from .merging import concat
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
'drop',
|
|
29
|
+
'drop_while',
|
|
30
|
+
'take',
|
|
31
|
+
'take_while',
|
|
32
|
+
'take_split',
|
|
33
|
+
'take_while_split',
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## dropping and taking
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def drop[D](iterable: Iterable[D], n: int) -> Iterator[D]:
|
|
41
|
+
"""Drop the next n values from iterable.
|
|
42
|
+
|
|
43
|
+
:param iterable: iterable whose values are to be dropped
|
|
44
|
+
:param n: number of values to be dropped
|
|
45
|
+
:return: an iterator of the remaining values
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
iterator = iter(iterable)
|
|
49
|
+
for _ in range(n):
|
|
50
|
+
try:
|
|
51
|
+
next(iterator)
|
|
52
|
+
except StopIteration:
|
|
53
|
+
break
|
|
54
|
+
return iterator
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def drop_while[D](
|
|
58
|
+
iterable: Iterable[D],
|
|
59
|
+
pred: Callable[[D], bool]
|
|
60
|
+
) -> Iterator[D]:
|
|
61
|
+
"""Drop initial values from iterable while predicate is true.
|
|
62
|
+
|
|
63
|
+
:param iterable: iterable whose values are to be dropped
|
|
64
|
+
:param pred: Boolean valued function, the "predicate"
|
|
65
|
+
:return: an iterator beginning where pred returned false
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
iterator = iter(iterable)
|
|
69
|
+
while True:
|
|
70
|
+
try:
|
|
71
|
+
value = next(iterator)
|
|
72
|
+
if not pred(value):
|
|
73
|
+
iterator = concat((value,), iterator)
|
|
74
|
+
break
|
|
75
|
+
except StopIteration:
|
|
76
|
+
break
|
|
77
|
+
return iterator
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def take[D](iterable: Iterable[D], n: int) -> Iterator[D]:
|
|
81
|
+
"""Return an iterator of up to n initial values of an iterable.
|
|
82
|
+
|
|
83
|
+
:param iterable: iterable providing the values to be taken
|
|
84
|
+
:param n: number of values to be dropped
|
|
85
|
+
:return: an iterator of up to n initial values from iterable
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
iterator = iter(iterable)
|
|
89
|
+
for _ in range(n):
|
|
90
|
+
try:
|
|
91
|
+
value = next(iterator)
|
|
92
|
+
yield value
|
|
93
|
+
except StopIteration:
|
|
94
|
+
break
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def take_while[D](
|
|
98
|
+
iterable: Iterable[D],
|
|
99
|
+
pred: Callable[[D], bool]
|
|
100
|
+
) -> Iterator[D]:
|
|
101
|
+
"""Yield values from iterable while predicate is true.
|
|
102
|
+
|
|
103
|
+
.. warning::
|
|
104
|
+
Risk of value loss if iterable is multiple referenced iterator.
|
|
105
|
+
|
|
106
|
+
:param iterable: iterable providing the values to be taken
|
|
107
|
+
:param pred: Boolean valued function, the "predicate"
|
|
108
|
+
:return: an Iterator of of values from iterable until predicate false
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
iterator = iter(iterable)
|
|
112
|
+
while True:
|
|
113
|
+
try:
|
|
114
|
+
value = next(iterator)
|
|
115
|
+
if pred(value):
|
|
116
|
+
yield value
|
|
117
|
+
else:
|
|
118
|
+
break
|
|
119
|
+
except StopIteration:
|
|
120
|
+
break
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def take_split[D](
|
|
124
|
+
iterable: Iterable[D],
|
|
125
|
+
n: int
|
|
126
|
+
) -> tuple[Iterator[D], Iterator[D]]:
|
|
127
|
+
"""Same as take except also return iterator of remaining values.
|
|
128
|
+
|
|
129
|
+
.. Warning::
|
|
130
|
+
|
|
131
|
+
**CONTRACT:** Do not access the second returned iterator until the
|
|
132
|
+
first one is exhausted.
|
|
133
|
+
|
|
134
|
+
:param iterable: iterable providing the values to be taken
|
|
135
|
+
:param n: maximum number of values to be taken
|
|
136
|
+
:return: an iterator of values taken and an iterator of remaining values
|
|
137
|
+
|
|
138
|
+
"""
|
|
139
|
+
iterator = iter(iterable)
|
|
140
|
+
itn = take(iterator, n)
|
|
141
|
+
|
|
142
|
+
return itn, iterator
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def take_while_split[D](
|
|
146
|
+
iterable: Iterable[D], pred: Callable[[D], bool]
|
|
147
|
+
) -> tuple[Iterator[D], Iterator[D]]:
|
|
148
|
+
"""Yield values from iterable while predicate is true.
|
|
149
|
+
|
|
150
|
+
.. Warning::
|
|
151
|
+
|
|
152
|
+
**CONTRACT:** Do not access the second returned iterator until
|
|
153
|
+
the first one is exhausted.
|
|
154
|
+
|
|
155
|
+
:param iterable: iterable providing the values to be taken
|
|
156
|
+
:param pred: Boolean valued function
|
|
157
|
+
:return: tuple containing an iterator of values taken and an iterator of remaining values
|
|
158
|
+
|
|
159
|
+
"""
|
|
160
|
+
def _take_while(
|
|
161
|
+
iterator: Iterator[D], pred: Callable[[D], bool], val: Box[D]
|
|
162
|
+
) -> Iterator[D]:
|
|
163
|
+
while True:
|
|
164
|
+
try:
|
|
165
|
+
val.put(next(iterator))
|
|
166
|
+
if pred(val.get()):
|
|
167
|
+
yield val.pop()
|
|
168
|
+
else:
|
|
169
|
+
break
|
|
170
|
+
except StopIteration:
|
|
171
|
+
break
|
|
172
|
+
|
|
173
|
+
iterator = iter(iterable)
|
|
174
|
+
value: Box[D] = Box()
|
|
175
|
+
it_pred = _take_while(iterator, pred, value)
|
|
176
|
+
|
|
177
|
+
return (it_pred, concat(value, iterator))
|
|
@@ -0,0 +1,294 @@
|
|
|
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
|
+
"""Module pythonic_fp.iterables.folding"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
__author__ = 'Geoffrey R. Scheller'
|
|
20
|
+
__copyright__ = 'Copyright (c) 2023-2025 Geoffrey R. Scheller'
|
|
21
|
+
__license__ = 'Apache License 2.0'
|
|
22
|
+
|
|
23
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
24
|
+
from typing import cast, Never
|
|
25
|
+
from pythonic_fp.fptools.function import negate, swap
|
|
26
|
+
from pythonic_fp.fptools.maybe import MayBe
|
|
27
|
+
from pythonic_fp.singletons.novalue import NoValue
|
|
28
|
+
from .drop_take import drop_while, take_while_split
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
'accumulate',
|
|
32
|
+
'reduce_left',
|
|
33
|
+
'fold_left',
|
|
34
|
+
'maybe_fold_left',
|
|
35
|
+
'sc_reduce_left',
|
|
36
|
+
'sc_reduce_right',
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
def accumulate[D, L](
|
|
40
|
+
iterable: Iterable[D],
|
|
41
|
+
f: Callable[[L, D], L],
|
|
42
|
+
initial: L | NoValue = NoValue()
|
|
43
|
+
) -> Iterator[L]:
|
|
44
|
+
"""Returns an iterator of partial fold values.
|
|
45
|
+
|
|
46
|
+
A pure Python version of standard library's ``itertools.accumulate``
|
|
47
|
+
|
|
48
|
+
- function ``f`` does not default to addition (for typing flexibility)
|
|
49
|
+
- begins accumulation with an "optional" ``initial`` value
|
|
50
|
+
|
|
51
|
+
:param iterable: iterable to be folded
|
|
52
|
+
:param f: two parameter function, first parameter for accumulated value
|
|
53
|
+
:param initial: "optional" initial value to start fold
|
|
54
|
+
:return: an iterator of the intermediate fold values
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
it = iter(iterable)
|
|
58
|
+
try:
|
|
59
|
+
it0 = next(it)
|
|
60
|
+
except StopIteration:
|
|
61
|
+
if initial is NoValue():
|
|
62
|
+
return
|
|
63
|
+
yield cast(L, initial)
|
|
64
|
+
else:
|
|
65
|
+
if initial is not NoValue():
|
|
66
|
+
init = cast(L, initial)
|
|
67
|
+
yield init
|
|
68
|
+
acc = f(init, it0)
|
|
69
|
+
for ii in it:
|
|
70
|
+
yield acc
|
|
71
|
+
acc = f(acc, ii)
|
|
72
|
+
yield acc
|
|
73
|
+
else:
|
|
74
|
+
acc = cast(L, it0) # in this case L = D
|
|
75
|
+
for ii in it:
|
|
76
|
+
yield acc
|
|
77
|
+
acc = f(acc, ii)
|
|
78
|
+
yield acc
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def reduce_left[D](
|
|
82
|
+
iterable: Iterable[D],
|
|
83
|
+
f: Callable[[D, D], D]
|
|
84
|
+
) -> D | Never:
|
|
85
|
+
"""Fold an iterable left with a function.
|
|
86
|
+
|
|
87
|
+
.. Warning::
|
|
88
|
+
|
|
89
|
+
This function never return if given an infinite iterable.
|
|
90
|
+
|
|
91
|
+
.. Warning::
|
|
92
|
+
|
|
93
|
+
This function does not catch or re-raises exceptions from ``f``.
|
|
94
|
+
|
|
95
|
+
:param iterable: iterable to be reduced (folded)
|
|
96
|
+
:param f: two parameter function, first parameter for accumulated value
|
|
97
|
+
:return: reduced value from the iterable
|
|
98
|
+
:raises StopIteration: when called on an empty iterable
|
|
99
|
+
:raises Exception: does not catch any exceptions from ``f``
|
|
100
|
+
|
|
101
|
+
"""
|
|
102
|
+
it = iter(iterable)
|
|
103
|
+
try:
|
|
104
|
+
acc = next(it)
|
|
105
|
+
except StopIteration as exc:
|
|
106
|
+
msg = 'Attempt to reduce an empty iterable?'
|
|
107
|
+
raise StopIteration(msg) from exc
|
|
108
|
+
|
|
109
|
+
for v in it:
|
|
110
|
+
acc = f(acc, v)
|
|
111
|
+
|
|
112
|
+
return acc
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def fold_left[D, L](
|
|
116
|
+
iterable: Iterable[D],
|
|
117
|
+
f: Callable[[L, D], L],
|
|
118
|
+
initial: L
|
|
119
|
+
) -> L | Never:
|
|
120
|
+
"""Fold an iterable left with a function and initial value.
|
|
121
|
+
|
|
122
|
+
- not restricted to ``__add__`` for the folding function
|
|
123
|
+
- initial value required, does not default to ``0`` for initial value
|
|
124
|
+
- handles non-numeric data just find
|
|
125
|
+
|
|
126
|
+
.. Warning::
|
|
127
|
+
|
|
128
|
+
This function never return if given an infinite iterable.
|
|
129
|
+
|
|
130
|
+
.. Warning::
|
|
131
|
+
|
|
132
|
+
This function does not catch any exceptions ``f`` may raise.
|
|
133
|
+
|
|
134
|
+
:param iterable: iterable to be folded
|
|
135
|
+
:param f: two parameter function, first parameter for accumulated value
|
|
136
|
+
:param initial: mandatory initial value to start fold
|
|
137
|
+
:return: the folded value
|
|
138
|
+
|
|
139
|
+
"""
|
|
140
|
+
acc = initial
|
|
141
|
+
for v in iterable:
|
|
142
|
+
acc = f(acc, v)
|
|
143
|
+
return acc
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def maybe_fold_left[D, L](
|
|
147
|
+
iterable: Iterable[D],
|
|
148
|
+
f: Callable[[L, D], L],
|
|
149
|
+
initial: L | NoValue = NoValue()
|
|
150
|
+
) -> MayBe[L] | Never:
|
|
151
|
+
"""Folds an iterable left with an "optional" initial value..
|
|
152
|
+
|
|
153
|
+
- when an initial value is not given then ``L = D``
|
|
154
|
+
- if iterable empty and no ``initial`` value given, return ``MayBe()``
|
|
155
|
+
|
|
156
|
+
.. Warning::
|
|
157
|
+
|
|
158
|
+
This function never return if given an infinite iterable.
|
|
159
|
+
|
|
160
|
+
.. Warning::
|
|
161
|
+
|
|
162
|
+
This function returns a ``MayBe()`` when ``f`` raises any
|
|
163
|
+
exception what-so-ever. The exception is thrown away.
|
|
164
|
+
|
|
165
|
+
:param iterable: The iterable to be folded.
|
|
166
|
+
:param f: First argument is for the accumulated value.
|
|
167
|
+
:param initial: Mandatory initial value to start fold.
|
|
168
|
+
:return: MayBe of a successfully folded value, otherwise MayBe()
|
|
169
|
+
|
|
170
|
+
"""
|
|
171
|
+
acc: L
|
|
172
|
+
it = iter(iterable)
|
|
173
|
+
if initial is NoValue():
|
|
174
|
+
try:
|
|
175
|
+
acc = cast(L, next(it)) # in this case L = D
|
|
176
|
+
except StopIteration:
|
|
177
|
+
return MayBe()
|
|
178
|
+
else:
|
|
179
|
+
acc = cast(L, initial)
|
|
180
|
+
|
|
181
|
+
for v in it:
|
|
182
|
+
try:
|
|
183
|
+
acc = f(acc, v)
|
|
184
|
+
except Exception:
|
|
185
|
+
return MayBe()
|
|
186
|
+
|
|
187
|
+
return MayBe(acc)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def sc_reduce_left[D](
|
|
191
|
+
iterable: Iterable[D],
|
|
192
|
+
f: Callable[[D, D], D],
|
|
193
|
+
start: Callable[[D], bool] = (lambda d: True),
|
|
194
|
+
stop: Callable[[D], bool] = (lambda d: False),
|
|
195
|
+
include_start: bool = True,
|
|
196
|
+
include_stop: bool = True,
|
|
197
|
+
) -> tuple[MayBe[D], Iterator[D]]:
|
|
198
|
+
"""Short circuit version of a left reduce.
|
|
199
|
+
|
|
200
|
+
Useful for infinite iterables.
|
|
201
|
+
|
|
202
|
+
Behavior for default arguments will
|
|
203
|
+
|
|
204
|
+
- left reduce finite iterable
|
|
205
|
+
- start folding immediately
|
|
206
|
+
- continue folding until end (of a possibly infinite iterable)
|
|
207
|
+
|
|
208
|
+
:param iterable: iterable to be reduced from the left
|
|
209
|
+
:param f: two parameter function, first parameter for accumulated value
|
|
210
|
+
:param start: delay starting the fold until it returns true
|
|
211
|
+
:param stop: prematurely stop the fold when it returns true
|
|
212
|
+
:param include_start: if true, include fold starting value in fold
|
|
213
|
+
:param include_stop: if true, include stopping value in fold
|
|
214
|
+
:return: tuple of a MayBe of the folded value and iterator of remaining iterables
|
|
215
|
+
|
|
216
|
+
"""
|
|
217
|
+
it_start = drop_while(iterable, negate(start))
|
|
218
|
+
if not include_start:
|
|
219
|
+
try:
|
|
220
|
+
next(it_start)
|
|
221
|
+
except StopIteration:
|
|
222
|
+
pass
|
|
223
|
+
it_reduce, it_rest = take_while_split(it_start, negate(stop))
|
|
224
|
+
mb_reduced = maybe_fold_left(it_reduce, f)
|
|
225
|
+
if include_stop:
|
|
226
|
+
if mb_reduced:
|
|
227
|
+
try:
|
|
228
|
+
last = next(it_rest)
|
|
229
|
+
mb_reduced = MayBe(f(mb_reduced.get(), last))
|
|
230
|
+
except StopIteration:
|
|
231
|
+
pass
|
|
232
|
+
else:
|
|
233
|
+
try:
|
|
234
|
+
last = next(it_rest)
|
|
235
|
+
mb_reduced = MayBe(last)
|
|
236
|
+
except StopIteration:
|
|
237
|
+
pass
|
|
238
|
+
|
|
239
|
+
return (mb_reduced, it_rest)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def sc_reduce_right[D](
|
|
243
|
+
iterable: Iterable[D],
|
|
244
|
+
f: Callable[[D, D], D],
|
|
245
|
+
start: Callable[[D], bool] = (lambda d: False),
|
|
246
|
+
stop: Callable[[D], bool] = (lambda d: False),
|
|
247
|
+
include_start: bool = True,
|
|
248
|
+
include_stop: bool = True,
|
|
249
|
+
) -> tuple[MayBe[D], Iterator[D]]:
|
|
250
|
+
"""Short circuit version of a right reduce.
|
|
251
|
+
|
|
252
|
+
Useful for infinite and non-reversible iterables.
|
|
253
|
+
|
|
254
|
+
Behavior for default arguments will
|
|
255
|
+
|
|
256
|
+
- right reduce finite iterable
|
|
257
|
+
- start folding at end (of a possibly infinite iterable)
|
|
258
|
+
- continue reducing right until beginning
|
|
259
|
+
|
|
260
|
+
:param iterable: iterable to be reduced from the right
|
|
261
|
+
:param f: two parameter function, second parameter for accumulated value
|
|
262
|
+
:param start: delay starting the fold until it returns true
|
|
263
|
+
:param stop: prematurely stop the fold when it returns true
|
|
264
|
+
:param include_start: if true, include fold starting value in fold
|
|
265
|
+
:param include_stop: if true, include stopping value in fold
|
|
266
|
+
:return: tuple of a MayBe of the folded value and iterator of remaining iterables
|
|
267
|
+
|
|
268
|
+
"""
|
|
269
|
+
it_start, it_rest = take_while_split(iterable, negate(start))
|
|
270
|
+
list1 = list(it_start)
|
|
271
|
+
if include_start:
|
|
272
|
+
try:
|
|
273
|
+
begin = next(it_rest)
|
|
274
|
+
except StopIteration:
|
|
275
|
+
pass
|
|
276
|
+
else:
|
|
277
|
+
list1.append(begin)
|
|
278
|
+
|
|
279
|
+
list1.reverse()
|
|
280
|
+
it_reduce, it_stop = take_while_split(list1, negate(stop))
|
|
281
|
+
|
|
282
|
+
mb_reduced = maybe_fold_left(it_reduce, swap(f))
|
|
283
|
+
if include_stop:
|
|
284
|
+
try:
|
|
285
|
+
end = next(it_stop)
|
|
286
|
+
except StopIteration:
|
|
287
|
+
pass
|
|
288
|
+
else:
|
|
289
|
+
if mb_reduced:
|
|
290
|
+
mb_reduced = MayBe(f(end, mb_reduced.get()))
|
|
291
|
+
else:
|
|
292
|
+
mb_reduced = MayBe(end)
|
|
293
|
+
|
|
294
|
+
return (mb_reduced, it_rest)
|
|
@@ -0,0 +1,148 @@
|
|
|
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
|
+
"""Module pythonic_fp.iterables.merging"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from collections.abc import Iterable, Iterator
|
|
20
|
+
from enum import auto, Enum
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
'MergeEnum',
|
|
24
|
+
'concat',
|
|
25
|
+
'merge',
|
|
26
|
+
'exhaust',
|
|
27
|
+
'blend',
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
class MergeEnum(Enum):
|
|
31
|
+
"""Iterable Blending Enums.
|
|
32
|
+
|
|
33
|
+
- **MergeEnum.Concat:** Concatenate first to last
|
|
34
|
+
- **MergeEnum.Merge:** Merge until one is exhausted
|
|
35
|
+
- **MergeEnum.Exhaust:** Merge until all are exhausted
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
Concat = auto()
|
|
39
|
+
Merge = auto()
|
|
40
|
+
Exhaust = auto()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def concat[D](*iterables: Iterable[D]) -> Iterator[D]:
|
|
44
|
+
"""Sequentially concatenate multiple iterables together.
|
|
45
|
+
|
|
46
|
+
.. warning::
|
|
47
|
+
An infinite iterable will prevent subsequent iterables from
|
|
48
|
+
yielding any values.
|
|
49
|
+
|
|
50
|
+
.. note::
|
|
51
|
+
|
|
52
|
+
Performant to the standard library's ``itertools.chain``.
|
|
53
|
+
|
|
54
|
+
:param iterables: iterables to concatenate
|
|
55
|
+
:return: iterator of concatenated values from the iterables
|
|
56
|
+
|
|
57
|
+
"""
|
|
58
|
+
for iterator in map(lambda x: iter(x), iterables):
|
|
59
|
+
while True:
|
|
60
|
+
try:
|
|
61
|
+
value = next(iterator)
|
|
62
|
+
yield value
|
|
63
|
+
except StopIteration:
|
|
64
|
+
break
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def merge[D](
|
|
68
|
+
*iterables: Iterable[D],
|
|
69
|
+
yield_partials: bool = False
|
|
70
|
+
) -> Iterator[D]:
|
|
71
|
+
"""Merge together ``iterables`` until one is exhausted.
|
|
72
|
+
|
|
73
|
+
.. note::
|
|
74
|
+
|
|
75
|
+
When ``yield_partials`` is true, then any unmatched values from other iterables
|
|
76
|
+
already yielded when the first iterable is exhausted are yielded. This prevents
|
|
77
|
+
data lose if any of the iterables are iterators with external references.
|
|
78
|
+
|
|
79
|
+
:param iterables: iterables to merge until one gets exhausted
|
|
80
|
+
:param yield_partials: yield any unpaired yielded values from other iterables
|
|
81
|
+
:return: iterator of merged values from the iterables until one is exhausted
|
|
82
|
+
|
|
83
|
+
"""
|
|
84
|
+
iter_list = list(map(lambda x: iter(x), iterables))
|
|
85
|
+
values = []
|
|
86
|
+
if (num_iters := len(iter_list)) > 0:
|
|
87
|
+
while True:
|
|
88
|
+
try:
|
|
89
|
+
for ii in range(num_iters):
|
|
90
|
+
values.append(next(iter_list[ii]))
|
|
91
|
+
yield from values
|
|
92
|
+
values.clear()
|
|
93
|
+
except StopIteration:
|
|
94
|
+
break
|
|
95
|
+
if yield_partials:
|
|
96
|
+
yield from values
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def exhaust[D](*iterables: Iterable[D]) -> Iterator[D]:
|
|
100
|
+
"""Merge together multiple iterables until all are exhausted.
|
|
101
|
+
|
|
102
|
+
:param iterables: iterables to exhaustively merge
|
|
103
|
+
:return: iterator of merged values from the iterables until all are exhausted
|
|
104
|
+
|
|
105
|
+
"""
|
|
106
|
+
iter_list = list(map(lambda x: iter(x), iterables))
|
|
107
|
+
if (num_iters := len(iter_list)) > 0:
|
|
108
|
+
ii = 0
|
|
109
|
+
values = []
|
|
110
|
+
while True:
|
|
111
|
+
try:
|
|
112
|
+
while ii < num_iters:
|
|
113
|
+
values.append(next(iter_list[ii]))
|
|
114
|
+
ii += 1
|
|
115
|
+
yield from values
|
|
116
|
+
ii = 0
|
|
117
|
+
values.clear()
|
|
118
|
+
except StopIteration:
|
|
119
|
+
num_iters -= 1
|
|
120
|
+
if num_iters < 1:
|
|
121
|
+
break
|
|
122
|
+
del iter_list[ii]
|
|
123
|
+
|
|
124
|
+
yield from values
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def blend[D](*iterables: Iterable[D],
|
|
128
|
+
merge_enum: MergeEnum = MergeEnum.Concat,
|
|
129
|
+
yield_partials: bool = False
|
|
130
|
+
) -> Iterator[D]:
|
|
131
|
+
"""Base merge behavior on name only parameters.
|
|
132
|
+
|
|
133
|
+
:param iterables: iterables to blend together
|
|
134
|
+
:param merge_enum: MergeEnum to determine merging behavior
|
|
135
|
+
:param yield_partials: yield yielded unpaired values from other iterables
|
|
136
|
+
:return: an iterator of type D
|
|
137
|
+
:raises ValueError: when an unknown MergeEnum is given
|
|
138
|
+
|
|
139
|
+
"""
|
|
140
|
+
match merge_enum:
|
|
141
|
+
case MergeEnum.Concat:
|
|
142
|
+
return concat(*iterables)
|
|
143
|
+
case MergeEnum.Merge:
|
|
144
|
+
return merge(*iterables, yield_partials = yield_partials)
|
|
145
|
+
case MergeEnum.Exhaust:
|
|
146
|
+
return exhaust(*iterables)
|
|
147
|
+
|
|
148
|
+
raise ValueError('Unknown MergeEnum given')
|
|
File without changes
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
|
-
Name: pythonic-fp
|
|
3
|
-
Version:
|
|
4
|
-
Summary:
|
|
2
|
+
Name: pythonic-fp-iterables
|
|
3
|
+
Version: 5.0.0
|
|
4
|
+
Summary: Library of functions for iterating iterables.
|
|
5
5
|
Keywords: iterables,iterators
|
|
6
6
|
Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
|
|
7
7
|
Requires-Python: >=3.12
|
|
8
|
-
Description-Content-Type: text/
|
|
8
|
+
Description-Content-Type: text/x-rst
|
|
9
9
|
Classifier: Development Status :: 5 - Production/Stable
|
|
10
10
|
Classifier: Framework :: Pytest
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
@@ -14,38 +14,47 @@ 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
|
|
18
|
-
Requires-Dist: pythonic-fp
|
|
19
|
-
Requires-Dist: pythonic-fp
|
|
20
|
-
|
|
21
|
-
Project-URL:
|
|
17
|
+
Requires-Dist: pythonic-fp-fptools>=5.0.0
|
|
18
|
+
Requires-Dist: pythonic-fp>=1.1.0
|
|
19
|
+
Requires-Dist: pythonic-fp-singletons>=1.0.0
|
|
20
|
+
Requires-Dist: pytest>=8.4.1 ; extra == "test"
|
|
21
|
+
Project-URL: Changelog, https://github.com/grscheller/pythonic-fp-iterables/blob/main/CHANGELOG.rst
|
|
22
|
+
Project-URL: Documentation, https://grscheller.github.io/pythonic-fp/iterables/development/build/html/releases.html
|
|
22
23
|
Project-URL: Homepage, https://github.com/grscheller/pythonic-fp/blob/main/README.md
|
|
23
24
|
Project-URL: Source, https://github.com/grscheller/pythonic-fp-iterables
|
|
25
|
+
Provides-Extra: test
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
Pythonic FP - Tools for iterables
|
|
28
|
+
=================================
|
|
26
29
|
|
|
27
30
|
PyPI project
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
`pythonic-fp.iterators <https://pypi.org/project/pythonic-fp.iterables>`_.
|
|
32
|
+
|
|
33
|
+
Library of functions for iterating iterables.
|
|
30
34
|
|
|
31
35
|
- Concatenating and merging iterables
|
|
32
36
|
- Dropping and taking values from iterables
|
|
33
37
|
- Reducing and accumulating iterables
|
|
34
38
|
- Assumptions
|
|
39
|
+
|
|
35
40
|
- iterables are not necessarily iterators
|
|
36
41
|
- at all times iterator protocol is assumed to be followed
|
|
42
|
+
|
|
37
43
|
- all iterators are assumed to be iterable
|
|
38
|
-
- for all iterators
|
|
44
|
+
- for all iterators ``foo`` we assume ``iter(foo) is foo``
|
|
39
45
|
|
|
40
|
-
This PyPI project is part of of the
|
|
41
|
-
|
|
46
|
+
This PyPI project is part of of the
|
|
47
|
+
`pythonic-fp namespace projects <https://github.com/grscheller/pythonic-fp/blob/main/README.md>`_
|
|
42
48
|
|
|
43
|
-
|
|
49
|
+
Documentation
|
|
50
|
+
-------------
|
|
44
51
|
|
|
45
|
-
Documentation hosted on
|
|
46
|
-
|
|
52
|
+
Documentation for this project is hosted on
|
|
53
|
+
`GitHub Pages
|
|
54
|
+
<https://grscheller.github.io/pythonic-fp/iterables/development/build/html>`_.
|
|
47
55
|
|
|
48
|
-
|
|
56
|
+
Copyright and License
|
|
57
|
+
---------------------
|
|
49
58
|
|
|
50
59
|
Copyright (c) 2023-2025 Geoffrey R. Scheller. Licensed under the Apache
|
|
51
60
|
License, Version 2.0. See the LICENSE file for details.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
pythonic_fp/iterables/__init__.py,sha256=gtTAk0cCZ4kjfx54hZcwURNXYg02BNr3nKcOP5gdJEk,1534
|
|
2
|
+
pythonic_fp/iterables/drop_take.py,sha256=70K6q0DGumK0XHFgPCpLK-lLDfNTkBzzjb2P5uttEqM,4940
|
|
3
|
+
pythonic_fp/iterables/folding.py,sha256=o3m7MNCWtlPoUsQUrXJ-xhrGsb3Nxp-iJJ7xJE5bbo0,8925
|
|
4
|
+
pythonic_fp/iterables/merging.py,sha256=mhFgSuIFb64wq9gnNkk63zoh92T7lkWB16VQ2L8t3-4,4530
|
|
5
|
+
pythonic_fp/iterables/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
pythonic_fp_iterables-5.0.0.dist-info/licenses/LICENSE,sha256=IJMkVWQUVy-SonK6SMaMotDQ6Zb-lVi2bUTiGp1GUO8,10774
|
|
7
|
+
pythonic_fp_iterables-5.0.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
8
|
+
pythonic_fp_iterables-5.0.0.dist-info/METADATA,sha256=kFDuTmQMieyCjmD-KmXzZy5970iL39BV-QXMEGaDcxA,2212
|
|
9
|
+
pythonic_fp_iterables-5.0.0.dist-info/RECORD,,
|
pythonic_fp/iterables.py
DELETED
|
@@ -1,455 +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
|
-
"""Module fp.iterables - Iterator related tools
|
|
16
|
-
|
|
17
|
-
Library of iterator related functions and enumerations.
|
|
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
|
-
from __future__ import annotations
|
|
32
|
-
|
|
33
|
-
__author__ = 'Geoffrey R. Scheller'
|
|
34
|
-
__copyright__ = 'Copyright (c) 2023-2025 Geoffrey R. Scheller'
|
|
35
|
-
__license__ = 'Apache License 2.0'
|
|
36
|
-
|
|
37
|
-
from collections.abc import Callable, Iterable, Iterator
|
|
38
|
-
from enum import auto, Enum
|
|
39
|
-
from typing import cast, Never, TypeVar
|
|
40
|
-
from pythonic_fp.containers.box import Box
|
|
41
|
-
from pythonic_fp.containers.maybe import MayBe as MB
|
|
42
|
-
from pythonic_fp.fptools.function import negate, swap
|
|
43
|
-
from pythonic_fp.fptools.singletons import NoValue
|
|
44
|
-
|
|
45
|
-
__all__ = [
|
|
46
|
-
'FM',
|
|
47
|
-
'concat',
|
|
48
|
-
'merge',
|
|
49
|
-
'exhaust',
|
|
50
|
-
'drop',
|
|
51
|
-
'drop_while',
|
|
52
|
-
'take',
|
|
53
|
-
'take_while',
|
|
54
|
-
'take_split',
|
|
55
|
-
'take_while_split',
|
|
56
|
-
'accumulate',
|
|
57
|
-
'foldl',
|
|
58
|
-
'mb_fold_left',
|
|
59
|
-
'reduce_left',
|
|
60
|
-
'sc_reduce_left',
|
|
61
|
-
'sc_reduce_right',
|
|
62
|
-
]
|
|
63
|
-
|
|
64
|
-
D = TypeVar('D') # Needed only for pdoc documentation generation.
|
|
65
|
-
L = TypeVar('L') # Otherwise, ignored by both MyPy and Python.
|
|
66
|
-
|
|
67
|
-
# Iterate over multiple iterables
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class FM(Enum):
|
|
71
|
-
"""Types of iterable blending,
|
|
72
|
-
|
|
73
|
-
- **CONCAT:** Concatenate first to last
|
|
74
|
-
- **MERGE:** Merge until one is exhausted
|
|
75
|
-
- **EXHAUST:** Merge until all are exhausted
|
|
76
|
-
"""
|
|
77
|
-
|
|
78
|
-
CONCAT = auto()
|
|
79
|
-
MERGE = auto()
|
|
80
|
-
EXHAUST = auto()
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def concat[D](*iterables: Iterable[D]) -> Iterator[D]:
|
|
84
|
-
"""Sequentially concatenate multiple iterables together.
|
|
85
|
-
|
|
86
|
-
- pure Python version of standard library's ``itertools.chain``
|
|
87
|
-
- iterator sequentially yields each iterable until all are exhausted
|
|
88
|
-
- an infinite iterable will prevent subsequent iterables from yielding any values
|
|
89
|
-
- performant to ``itertools.chain``
|
|
90
|
-
"""
|
|
91
|
-
for iterator in map(lambda x: iter(x), iterables):
|
|
92
|
-
while True:
|
|
93
|
-
try:
|
|
94
|
-
value = next(iterator)
|
|
95
|
-
yield value
|
|
96
|
-
except StopIteration:
|
|
97
|
-
break
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def exhaust[D](*iterables: Iterable[D]) -> Iterator[D]:
|
|
101
|
-
"""Shuffle together multiple iterables until all are exhausted.
|
|
102
|
-
Iterator yields until all iterables are exhausted.
|
|
103
|
-
"""
|
|
104
|
-
iter_list = list(map(lambda x: iter(x), iterables))
|
|
105
|
-
if (num_iters := len(iter_list)) > 0:
|
|
106
|
-
ii = 0
|
|
107
|
-
values = []
|
|
108
|
-
while True:
|
|
109
|
-
try:
|
|
110
|
-
while ii < num_iters:
|
|
111
|
-
values.append(next(iter_list[ii]))
|
|
112
|
-
ii += 1
|
|
113
|
-
yield from values
|
|
114
|
-
ii = 0
|
|
115
|
-
values.clear()
|
|
116
|
-
except StopIteration:
|
|
117
|
-
num_iters -= 1
|
|
118
|
-
if num_iters < 1:
|
|
119
|
-
break
|
|
120
|
-
del iter_list[ii]
|
|
121
|
-
|
|
122
|
-
yield from values
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def merge[D](*iterables: Iterable[D], yield_partials: bool = False) -> Iterator[D]:
|
|
126
|
-
"""Shuffle together the ``iterables`` until one is exhausted.
|
|
127
|
-
|
|
128
|
-
- iterator yields until one of the iterables is exhausted
|
|
129
|
-
- if ``yield_partials`` is true,
|
|
130
|
-
|
|
131
|
-
- yield any unmatched yielded values from other iterables
|
|
132
|
-
- prevents data lose
|
|
133
|
-
|
|
134
|
-
- if any of the iterables are iterators with external references
|
|
135
|
-
"""
|
|
136
|
-
iter_list = list(map(lambda x: iter(x), iterables))
|
|
137
|
-
values = []
|
|
138
|
-
if (num_iters := len(iter_list)) > 0:
|
|
139
|
-
while True:
|
|
140
|
-
try:
|
|
141
|
-
for ii in range(num_iters):
|
|
142
|
-
values.append(next(iter_list[ii]))
|
|
143
|
-
yield from values
|
|
144
|
-
values.clear()
|
|
145
|
-
except StopIteration:
|
|
146
|
-
break
|
|
147
|
-
if yield_partials:
|
|
148
|
-
yield from values
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
## dropping and taking
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def drop[D](iterable: Iterable[D], n: int, /) -> Iterator[D]:
|
|
155
|
-
"""Drop the next ``n`` values from ``iterable``."""
|
|
156
|
-
it = iter(iterable)
|
|
157
|
-
for _ in range(n):
|
|
158
|
-
try:
|
|
159
|
-
next(it)
|
|
160
|
-
except StopIteration:
|
|
161
|
-
break
|
|
162
|
-
return it
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def drop_while[D](iterable: Iterable[D], pred: Callable[[D], bool], /) -> Iterator[D]:
|
|
166
|
-
"""Drop initial values from ``iterable`` while predicate is true."""
|
|
167
|
-
it = iter(iterable)
|
|
168
|
-
while True:
|
|
169
|
-
try:
|
|
170
|
-
value = next(it)
|
|
171
|
-
if not pred(value):
|
|
172
|
-
it = concat((value,), it)
|
|
173
|
-
break
|
|
174
|
-
except StopIteration:
|
|
175
|
-
break
|
|
176
|
-
return it
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
def take[D](iterable: Iterable[D], n: int, /) -> Iterator[D]:
|
|
180
|
-
"""Return an iterator of up to ``n`` initial values of an iterable"""
|
|
181
|
-
it = iter(iterable)
|
|
182
|
-
for _ in range(n):
|
|
183
|
-
try:
|
|
184
|
-
value = next(it)
|
|
185
|
-
yield value
|
|
186
|
-
except StopIteration:
|
|
187
|
-
break
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
def take_split[D](iterable: Iterable[D], n: int, /) -> tuple[Iterator[D], Iterator[D]]:
|
|
191
|
-
"""Same as take except also return an iterator of the remaining values.
|
|
192
|
-
|
|
193
|
-
- return a tuple of
|
|
194
|
-
|
|
195
|
-
- an iterator of up to ``n`` initial values
|
|
196
|
-
- an iterator of the remaining vales of the ``iterable``
|
|
197
|
-
|
|
198
|
-
- Contract: do not access second iterator until first is exhausted
|
|
199
|
-
"""
|
|
200
|
-
it = iter(iterable)
|
|
201
|
-
itn = take(it, n)
|
|
202
|
-
|
|
203
|
-
return itn, it
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
def take_while[D](iterable: Iterable[D], pred: Callable[[D], bool], /) -> Iterator[D]:
|
|
207
|
-
"""Yield values from ``iterable`` while predicate is true.
|
|
208
|
-
|
|
209
|
-
.. warning::
|
|
210
|
-
Risk of value loss if iterable is multiple referenced iterator.
|
|
211
|
-
"""
|
|
212
|
-
it = iter(iterable)
|
|
213
|
-
while True:
|
|
214
|
-
try:
|
|
215
|
-
value = next(it)
|
|
216
|
-
if pred(value):
|
|
217
|
-
yield value
|
|
218
|
-
else:
|
|
219
|
-
break
|
|
220
|
-
except StopIteration:
|
|
221
|
-
break
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
def take_while_split[D](
|
|
225
|
-
iterable: Iterable[D], pred: Callable[[D], bool], /
|
|
226
|
-
) -> tuple[Iterator[D], Iterator[D]]:
|
|
227
|
-
"""Yield values from ``iterable`` while ``predicate`` is true.
|
|
228
|
-
|
|
229
|
-
- return a tuple of two iterators
|
|
230
|
-
|
|
231
|
-
- first of initial values where predicate is true, followed by first to fail
|
|
232
|
-
- second of the remaining values of the iterable after first failed value
|
|
233
|
-
|
|
234
|
-
- **Contract:** do not access second iterator until first is exhausted
|
|
235
|
-
"""
|
|
236
|
-
|
|
237
|
-
def _take_while(
|
|
238
|
-
it: Iterator[D], pred: Callable[[D], bool], val: Box[D]
|
|
239
|
-
) -> Iterator[D]:
|
|
240
|
-
while True:
|
|
241
|
-
try:
|
|
242
|
-
val.put(next(it))
|
|
243
|
-
if pred(val.get()):
|
|
244
|
-
yield val.pop()
|
|
245
|
-
else:
|
|
246
|
-
break
|
|
247
|
-
except StopIteration:
|
|
248
|
-
break
|
|
249
|
-
|
|
250
|
-
it = iter(iterable)
|
|
251
|
-
value: Box[D] = Box()
|
|
252
|
-
it_pred = _take_while(it, pred, value)
|
|
253
|
-
|
|
254
|
-
return (it_pred, concat(value, it))
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
## reducing and accumulating
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def accumulate[D, L](
|
|
261
|
-
iterable: Iterable[D], f: Callable[[L, D], L], initial: L | NoValue = NoValue(), /
|
|
262
|
-
) -> Iterator[L]:
|
|
263
|
-
"""Returns an iterator of accumulated values.
|
|
264
|
-
|
|
265
|
-
- pure Python version of standard library's ``itertools.accumulate``
|
|
266
|
-
- function ``f`` does not default to addition (for typing flexibility)
|
|
267
|
-
- begins accumulation with an optional ``initial`` value
|
|
268
|
-
"""
|
|
269
|
-
it = iter(iterable)
|
|
270
|
-
try:
|
|
271
|
-
it0 = next(it)
|
|
272
|
-
except StopIteration:
|
|
273
|
-
if initial is NoValue():
|
|
274
|
-
return
|
|
275
|
-
yield cast(L, initial)
|
|
276
|
-
else:
|
|
277
|
-
if initial is not NoValue():
|
|
278
|
-
init = cast(L, initial)
|
|
279
|
-
yield init
|
|
280
|
-
acc = f(init, it0)
|
|
281
|
-
for ii in it:
|
|
282
|
-
yield acc
|
|
283
|
-
acc = f(acc, ii)
|
|
284
|
-
yield acc
|
|
285
|
-
else:
|
|
286
|
-
acc = cast(L, it0) # in this case L = D
|
|
287
|
-
for ii in it:
|
|
288
|
-
yield acc
|
|
289
|
-
acc = f(acc, ii)
|
|
290
|
-
yield acc
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
def reduce_left[D](iterable: Iterable[D], f: Callable[[D, D], D], /) -> D | Never:
|
|
294
|
-
"""Fold an iterable left with a function.
|
|
295
|
-
|
|
296
|
-
- traditional FP type order given for function ``f``
|
|
297
|
-
- if iterable empty, ``StopIteration`` exception raised
|
|
298
|
-
- does not catch any exceptions ``f`` may raise
|
|
299
|
-
- never returns if ``iterable`` generates an infinite iterator
|
|
300
|
-
"""
|
|
301
|
-
it = iter(iterable)
|
|
302
|
-
try:
|
|
303
|
-
acc = next(it)
|
|
304
|
-
except StopIteration as exc:
|
|
305
|
-
msg = 'Attemped to reduce an empty iterable.'
|
|
306
|
-
raise StopIteration(msg) from exc
|
|
307
|
-
|
|
308
|
-
for v in it:
|
|
309
|
-
acc = f(acc, v)
|
|
310
|
-
|
|
311
|
-
return acc
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
def foldl[D, L](
|
|
315
|
-
iterable: Iterable[D], f: Callable[[L, D], L], initial: L, /
|
|
316
|
-
) -> L | Never:
|
|
317
|
-
"""Folds an iterable left with a function and initial value.
|
|
318
|
-
|
|
319
|
-
- traditional FP type order given for function ``f``
|
|
320
|
-
- does not catch any exceptions ``f`` may raise
|
|
321
|
-
- like builtin ``sum`` for Python >=3.8 except
|
|
322
|
-
|
|
323
|
-
- not restricted to ``__add__`` for the folding function
|
|
324
|
-
- initial value required, does not default to ``0`` for initial value
|
|
325
|
-
- handles non-numeric data just find
|
|
326
|
-
|
|
327
|
-
- never returns if ``iterable`` generates an infinite iterator
|
|
328
|
-
"""
|
|
329
|
-
acc = initial
|
|
330
|
-
for v in iterable:
|
|
331
|
-
acc = f(acc, v)
|
|
332
|
-
return acc
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
def mb_fold_left[L, D](
|
|
336
|
-
iterable: Iterable[D], f: Callable[[L, D], L], initial: L | NoValue = NoValue()
|
|
337
|
-
) -> MB[L]:
|
|
338
|
-
"""Folds an iterable left with optional initial value.
|
|
339
|
-
|
|
340
|
-
- traditional FP type order given for function ``f``
|
|
341
|
-
- when an initial value is not given then ``~L = ~D``
|
|
342
|
-
- if iterable empty and no ``initial`` value given, return ``MB()``
|
|
343
|
-
- never returns if iterable generates an infinite iterator
|
|
344
|
-
"""
|
|
345
|
-
acc: L
|
|
346
|
-
it = iter(iterable)
|
|
347
|
-
if initial is NoValue():
|
|
348
|
-
try:
|
|
349
|
-
acc = cast(L, next(it)) # in this case L = D
|
|
350
|
-
except StopIteration:
|
|
351
|
-
return MB()
|
|
352
|
-
else:
|
|
353
|
-
acc = cast(L, initial)
|
|
354
|
-
|
|
355
|
-
for v in it:
|
|
356
|
-
try:
|
|
357
|
-
acc = f(acc, v)
|
|
358
|
-
except Exception:
|
|
359
|
-
return MB()
|
|
360
|
-
|
|
361
|
-
return MB(acc)
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
def sc_reduce_left[D](
|
|
365
|
-
iterable: Iterable[D],
|
|
366
|
-
f: Callable[[D, D], D],
|
|
367
|
-
/,
|
|
368
|
-
start: Callable[[D], bool] = (lambda d: True),
|
|
369
|
-
stop: Callable[[D], bool] = (lambda d: False),
|
|
370
|
-
include_start: bool = True,
|
|
371
|
-
include_stop: bool = True,
|
|
372
|
-
) -> tuple[MB[D], Iterator[D]]:
|
|
373
|
-
"""Short circuit version of a left reduce. Useful for infinite or iterables.
|
|
374
|
-
|
|
375
|
-
- Behavior for default arguments will
|
|
376
|
-
|
|
377
|
-
- left reduce finite iterable
|
|
378
|
-
- start folding immediately
|
|
379
|
-
- continue folding until end (of a possibly infinite iterable)
|
|
380
|
-
|
|
381
|
-
- Callable ``start`` delays starting the left reduce
|
|
382
|
-
- Callable ``stop`` prematurely stop the left reduce
|
|
383
|
-
"""
|
|
384
|
-
it_start = drop_while(iterable, negate(start))
|
|
385
|
-
if not include_start:
|
|
386
|
-
try:
|
|
387
|
-
next(it_start)
|
|
388
|
-
except StopIteration:
|
|
389
|
-
pass
|
|
390
|
-
it_reduce, it_rest = take_while_split(it_start, negate(stop))
|
|
391
|
-
mb_reduced = mb_fold_left(it_reduce, f)
|
|
392
|
-
if include_stop:
|
|
393
|
-
if mb_reduced:
|
|
394
|
-
try:
|
|
395
|
-
last = next(it_rest)
|
|
396
|
-
mb_reduced = MB(f(mb_reduced.get(), last))
|
|
397
|
-
except StopIteration:
|
|
398
|
-
pass
|
|
399
|
-
else:
|
|
400
|
-
try:
|
|
401
|
-
last = next(it_rest)
|
|
402
|
-
mb_reduced = MB(last)
|
|
403
|
-
except StopIteration:
|
|
404
|
-
pass
|
|
405
|
-
|
|
406
|
-
return (mb_reduced, it_rest)
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
def sc_reduce_right[D](
|
|
410
|
-
iterable: Iterable[D],
|
|
411
|
-
f: Callable[[D, D], D],
|
|
412
|
-
/,
|
|
413
|
-
start: Callable[[D], bool] = (lambda d: False),
|
|
414
|
-
stop: Callable[[D], bool] = (lambda d: False),
|
|
415
|
-
include_start: bool = True,
|
|
416
|
-
include_stop: bool = True,
|
|
417
|
-
) -> tuple[MB[D], Iterator[D]]:
|
|
418
|
-
"""Short circuit version of a right reduce. Useful for infinite or
|
|
419
|
-
non-reversible iterables.
|
|
420
|
-
|
|
421
|
-
- Behavior for default arguments will
|
|
422
|
-
|
|
423
|
-
- right reduce finite iterable
|
|
424
|
-
- start reducing at end (of a possibly infinite iterable)
|
|
425
|
-
- continue reducing right until beginning
|
|
426
|
-
|
|
427
|
-
- Callable ``start`` prematurely starts the right reduce
|
|
428
|
-
- Callable ``stop`` prematurely stops the right reduce
|
|
429
|
-
"""
|
|
430
|
-
it_start, it_rest = take_while_split(iterable, negate(start))
|
|
431
|
-
l1 = list(it_start)
|
|
432
|
-
if include_start:
|
|
433
|
-
try:
|
|
434
|
-
begin = next(it_rest)
|
|
435
|
-
except StopIteration:
|
|
436
|
-
pass
|
|
437
|
-
else:
|
|
438
|
-
l1.append(begin)
|
|
439
|
-
|
|
440
|
-
l1.reverse()
|
|
441
|
-
it_reduce, it_stop = take_while_split(l1, negate(stop))
|
|
442
|
-
|
|
443
|
-
mb_reduced = mb_fold_left(it_reduce, swap(f))
|
|
444
|
-
if include_stop:
|
|
445
|
-
try:
|
|
446
|
-
end = next(it_stop)
|
|
447
|
-
except StopIteration:
|
|
448
|
-
pass
|
|
449
|
-
else:
|
|
450
|
-
if mb_reduced:
|
|
451
|
-
mb_reduced = MB(f(end, mb_reduced.get()))
|
|
452
|
-
else:
|
|
453
|
-
mb_reduced = MB(end)
|
|
454
|
-
|
|
455
|
-
return (mb_reduced, it_rest)
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
pythonic_fp/iterables.py,sha256=Si4a4JcJhrStvpxqe8msaqiskcgrb3D8xJP2oN-6VG8,13194
|
|
2
|
-
pythonic_fp_iterables-3.0.0.dist-info/licenses/LICENSE,sha256=IJMkVWQUVy-SonK6SMaMotDQ6Zb-lVi2bUTiGp1GUO8,10774
|
|
3
|
-
pythonic_fp_iterables-3.0.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
4
|
-
pythonic_fp_iterables-3.0.0.dist-info/METADATA,sha256=9vZd4-jSvYbAXdXXSo6UwZHcLgrSEprn2J6cgyqUkeA,2059
|
|
5
|
-
pythonic_fp_iterables-3.0.0.dist-info/RECORD,,
|
|
File without changes
|
{pythonic_fp_iterables-3.0.0.dist-info → pythonic_fp_iterables-5.0.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|