pythonic-fp.iterables 4.0.0__py3-none-any.whl → 5.1.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.
@@ -0,0 +1,42 @@
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
+ """
16
+ Library of functions for iterating iterables
17
+ ============================================
18
+
19
+ +-----------+-------------------------------------------+
20
+ | Module | Description |
21
+ +===========+===========================================+
22
+ | merging | Concatenating and merging iterables |
23
+ +-----------+-------------------------------------------+
24
+ | drop_take | Dropping and taking values from iterables |
25
+ +-----------+-------------------------------------------+
26
+ | folding | Reducing and accumulating iterables |
27
+ +-----------+-------------------------------------------+
28
+
29
+ Assumptions
30
+ -----------
31
+
32
+ - iterables are not necessarily iterators
33
+ - at all times iterator protocol is assumed to be followed
34
+
35
+ - all iterators are assumed to be iterable
36
+ - for all iterators ``foobar`` we assume ``iter(foobar) is foobar``
37
+
38
+ """
39
+
40
+ __author__ = 'Geoffrey R. Scheller'
41
+ __copyright__ = 'Copyright (c) 2023-2025 Geoffrey R. Scheller'
42
+ __license__ = 'Apache License 2.0'
@@ -0,0 +1,165 @@
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
+ Module pythonic_fp.iterables.drop_take
17
+ """
18
+
19
+ from collections.abc import Callable, Iterable, Iterator
20
+ from pythonic_fp.gadgets.box import Box
21
+ from .merging import concat
22
+
23
+ __all__ = [
24
+ 'drop',
25
+ 'drop_while',
26
+ 'take',
27
+ 'take_while',
28
+ 'take_split',
29
+ 'take_while_split',
30
+ ]
31
+
32
+
33
+ ## dropping and taking
34
+
35
+
36
+ def drop[D](iterable: Iterable[D], n: int) -> Iterator[D]:
37
+ """Drop the next n values from iterable.
38
+
39
+ :param iterable: iterable whose values are to be dropped
40
+ :param n: number of values to be dropped
41
+ :returns: an iterator of the remaining values
42
+
43
+ """
44
+ iterator = iter(iterable)
45
+ for _ in range(n):
46
+ try:
47
+ next(iterator)
48
+ except StopIteration:
49
+ break
50
+ return iterator
51
+
52
+
53
+ def drop_while[D](iterable: Iterable[D], pred: Callable[[D], bool]) -> Iterator[D]:
54
+ """Drop initial values from iterable while predicate is true.
55
+
56
+ :param iterable: iterable whose values are to be dropped
57
+ :param pred: Boolean valued function, the "predicate"
58
+ :return: an iterator beginning where pred returned false
59
+
60
+ """
61
+ iterator = iter(iterable)
62
+ while True:
63
+ try:
64
+ value = next(iterator)
65
+ if not pred(value):
66
+ iterator = concat((value,), iterator)
67
+ break
68
+ except StopIteration:
69
+ break
70
+ return iterator
71
+
72
+
73
+ def take[D](iterable: Iterable[D], n: int) -> Iterator[D]:
74
+ """Return an iterator of up to n initial values of an iterable.
75
+
76
+ :param iterable: iterable providing the values to be taken
77
+ :param n: number of values to be dropped
78
+ :return: an iterator of up to n initial values from iterable
79
+
80
+ """
81
+ iterator = iter(iterable)
82
+ for _ in range(n):
83
+ try:
84
+ value = next(iterator)
85
+ yield value
86
+ except StopIteration:
87
+ break
88
+
89
+
90
+ def take_while[D](iterable: Iterable[D], pred: Callable[[D], bool]) -> Iterator[D]:
91
+ """Yield values from iterable while predicate is true.
92
+
93
+ .. warning::
94
+ Risk of value loss if iterable is multiple referenced iterator.
95
+
96
+ :param iterable: iterable providing the values to be taken
97
+ :param pred: Boolean valued function, the "predicate"
98
+ :return: an Iterator of of values from iterable until predicate false
99
+
100
+ """
101
+ iterator = iter(iterable)
102
+ while True:
103
+ try:
104
+ value = next(iterator)
105
+ if pred(value):
106
+ yield value
107
+ else:
108
+ break
109
+ except StopIteration:
110
+ break
111
+
112
+
113
+ def take_split[D](iterable: Iterable[D], n: int) -> tuple[Iterator[D], Iterator[D]]:
114
+ """Same as take except also return iterator of remaining values.
115
+
116
+ .. Warning::
117
+
118
+ **CONTRACT:** Do not access the second returned iterator until the
119
+ first one is exhausted.
120
+
121
+ :param iterable: iterable providing the values to be taken
122
+ :param n: maximum number of values to be taken
123
+ :return: an iterator of values taken and an iterator of remaining values
124
+
125
+ """
126
+ iterator = iter(iterable)
127
+ itn = take(iterator, n)
128
+
129
+ return itn, iterator
130
+
131
+
132
+ def take_while_split[D](
133
+ iterable: Iterable[D], pred: Callable[[D], bool]
134
+ ) -> tuple[Iterator[D], Iterator[D]]:
135
+ """Yield values from iterable while predicate is true.
136
+
137
+ .. Warning::
138
+
139
+ **CONTRACT:** Do not access the second returned iterator until
140
+ the first one is exhausted.
141
+
142
+ :param iterable: iterable providing the values to be taken
143
+ :param pred: Boolean valued function
144
+ :return: tuple containing an iterator of values taken and an iterator of remaining values
145
+
146
+ """
147
+
148
+ def _take_while(
149
+ iterator: Iterator[D], pred: Callable[[D], bool], val: Box[D]
150
+ ) -> Iterator[D]:
151
+ while True:
152
+ try:
153
+ val.put(next(iterator))
154
+ if pred(val.get()):
155
+ yield val.pop()
156
+ else:
157
+ break
158
+ except StopIteration:
159
+ break
160
+
161
+ iterator = iter(iterable)
162
+ value: Box[D] = Box()
163
+ it_pred = _take_while(iterator, pred, value)
164
+
165
+ return (it_pred, concat(value, iterator))
@@ -0,0 +1,33 @@
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
+ from collections.abc import Callable, Iterable, Iterator
16
+
17
+ __all__ = [
18
+ 'drop',
19
+ 'drop_while',
20
+ 'take',
21
+ 'take_while',
22
+ 'take_split',
23
+ 'take_while_split',
24
+ ]
25
+
26
+ def drop[D](iterable: Iterable[D], n: int) -> Iterator[D]: ...
27
+ def drop_while[D](iterable: Iterable[D], pred: Callable[[D], bool]) -> Iterator[D]: ...
28
+ def take[D](iterable: Iterable[D], n: int) -> Iterator[D]: ...
29
+ def take_while[D](iterable: Iterable[D], pred: Callable[[D], bool]) -> Iterator[D]: ...
30
+ def take_split[D](iterable: Iterable[D], n: int) -> tuple[Iterator[D], Iterator[D]]: ...
31
+ def take_while_split[D](
32
+ iterable: Iterable[D], pred: Callable[[D], bool]
33
+ ) -> tuple[Iterator[D], Iterator[D]]: ...
@@ -0,0 +1,280 @@
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 collections.abc import Callable, Iterable, Iterator
18
+ from typing import cast, Never
19
+ from pythonic_fp.fptools.function import negate, swap
20
+ from pythonic_fp.fptools.maybe import MayBe
21
+ from pythonic_fp.sentinels.novalue import NoValue
22
+ from .drop_take import drop_while, take_while_split
23
+
24
+ __all__ = [
25
+ 'accumulate',
26
+ 'reduce_left',
27
+ 'fold_left',
28
+ 'maybe_fold_left',
29
+ 'sc_reduce_left',
30
+ 'sc_reduce_right',
31
+ ]
32
+
33
+
34
+ def accumulate[D, L](
35
+ iterable: Iterable[D], f: Callable[[L, D], L], initial: L | NoValue = NoValue()
36
+ ) -> Iterator[L]:
37
+ """Returns an iterator of partial fold values.
38
+
39
+ A pure Python version of standard library's ``itertools.accumulate``
40
+
41
+ - function ``f`` does not default to addition (for typing flexibility)
42
+ - begins accumulation with an "optional" ``initial`` value
43
+
44
+ :param iterable: iterable to be folded
45
+ :param f: two parameter function, first parameter for accumulated value
46
+ :param initial: "optional" initial value to start fold
47
+ :return: an iterator of the intermediate fold values
48
+
49
+ """
50
+ it = iter(iterable)
51
+ try:
52
+ it0 = next(it)
53
+ except StopIteration:
54
+ if initial is NoValue():
55
+ return
56
+ yield cast(L, initial)
57
+ else:
58
+ if initial is not NoValue():
59
+ init = cast(L, initial)
60
+ yield init
61
+ acc = f(init, it0)
62
+ for ii in it:
63
+ yield acc
64
+ acc = f(acc, ii)
65
+ yield acc
66
+ else:
67
+ acc = cast(L, it0) # in this case L = D
68
+ for ii in it:
69
+ yield acc
70
+ acc = f(acc, ii)
71
+ yield acc
72
+
73
+
74
+ def reduce_left[D](iterable: Iterable[D], f: Callable[[D, D], D]) -> D | Never:
75
+ """Fold an iterable left with a function.
76
+
77
+ .. Warning::
78
+
79
+ This function never return if given an infinite iterable.
80
+
81
+ .. Warning::
82
+
83
+ This function does not catch or re-raises exceptions from ``f``.
84
+
85
+ :param iterable: iterable to be reduced (folded)
86
+ :param f: two parameter function, first parameter for accumulated value
87
+ :return: reduced value from the iterable
88
+ :raises StopIteration: when called on an empty iterable
89
+ :raises Exception: does not catch any exceptions from ``f``
90
+
91
+ """
92
+ it = iter(iterable)
93
+ try:
94
+ acc = next(it)
95
+ except StopIteration as exc:
96
+ msg = 'Attempt to reduce an empty iterable?'
97
+ raise StopIteration(msg) from exc
98
+
99
+ for v in it:
100
+ acc = f(acc, v)
101
+
102
+ return acc
103
+
104
+
105
+ def fold_left[D, L](
106
+ iterable: Iterable[D], f: Callable[[L, D], L], initial: L
107
+ ) -> L | Never:
108
+ """Fold an iterable left with a function and initial value.
109
+
110
+ - not restricted to ``__add__`` for the folding function
111
+ - initial value required, does not default to ``0`` for initial value
112
+ - handles non-numeric data just find
113
+
114
+ .. Warning::
115
+
116
+ This function never return if given an infinite iterable.
117
+
118
+ .. Warning::
119
+
120
+ This function does not catch any exceptions ``f`` may raise.
121
+
122
+ :param iterable: iterable to be folded
123
+ :param f: two parameter function, first parameter for accumulated value
124
+ :param initial: mandatory initial value to start fold
125
+ :return: the folded value
126
+
127
+ """
128
+ acc = initial
129
+ for v in iterable:
130
+ acc = f(acc, v)
131
+ return acc
132
+
133
+
134
+ def maybe_fold_left[D, L](
135
+ iterable: Iterable[D], f: Callable[[L, D], L], initial: L | NoValue = NoValue()
136
+ ) -> MayBe[L] | Never:
137
+ """Folds an iterable left with an "optional" initial value..
138
+
139
+ - when an initial value is not given then ``L = D``
140
+ - if iterable empty and no ``initial`` value given, return ``MayBe()``
141
+
142
+ .. Warning::
143
+
144
+ This function never return if given an infinite iterable.
145
+
146
+ .. Warning::
147
+
148
+ This function returns a ``MayBe()`` when ``f`` raises any
149
+ exception what-so-ever. The exception is thrown away.
150
+
151
+ :param iterable: The iterable to be folded.
152
+ :param f: First argument is for the accumulated value.
153
+ :param initial: Mandatory initial value to start fold.
154
+ :return: MayBe of a successfully folded value, otherwise MayBe()
155
+
156
+ """
157
+ acc: L
158
+ it = iter(iterable)
159
+ if initial is NoValue():
160
+ try:
161
+ acc = cast(L, next(it)) # in this case L = D
162
+ except StopIteration:
163
+ return MayBe()
164
+ else:
165
+ acc = cast(L, initial)
166
+
167
+ for v in it:
168
+ try:
169
+ acc = f(acc, v)
170
+ except Exception:
171
+ return MayBe()
172
+
173
+ return MayBe(acc)
174
+
175
+
176
+ def sc_reduce_left[D](
177
+ iterable: Iterable[D],
178
+ f: Callable[[D, D], D],
179
+ start: Callable[[D], bool] = (lambda d: True),
180
+ stop: Callable[[D], bool] = (lambda d: False),
181
+ include_start: bool = True,
182
+ include_stop: bool = True,
183
+ ) -> tuple[MayBe[D], Iterator[D]]:
184
+ """Short circuit version of a left reduce.
185
+
186
+ Useful for infinite iterables.
187
+
188
+ Behavior for default arguments will
189
+
190
+ - left reduce finite iterable
191
+ - start folding immediately
192
+ - continue folding until end (of a possibly infinite iterable)
193
+
194
+ :param iterable: iterable to be reduced from the left
195
+ :param f: two parameter function, first parameter for accumulated value
196
+ :param start: delay starting the fold until it returns true
197
+ :param stop: prematurely stop the fold when it returns true
198
+ :param include_start: if true, include fold starting value in fold
199
+ :param include_stop: if true, include stopping value in fold
200
+ :return: tuple of a MayBe of the folded value and iterator of remaining iterables
201
+
202
+ """
203
+ it_start = drop_while(iterable, negate(start))
204
+ if not include_start:
205
+ try:
206
+ next(it_start)
207
+ except StopIteration:
208
+ pass
209
+ it_reduce, it_rest = take_while_split(it_start, negate(stop))
210
+ mb_reduced = maybe_fold_left(it_reduce, f)
211
+ if include_stop:
212
+ if mb_reduced:
213
+ try:
214
+ last = next(it_rest)
215
+ mb_reduced = MayBe(f(mb_reduced.get(), last))
216
+ except StopIteration:
217
+ pass
218
+ else:
219
+ try:
220
+ last = next(it_rest)
221
+ mb_reduced = MayBe(last)
222
+ except StopIteration:
223
+ pass
224
+
225
+ return (mb_reduced, it_rest)
226
+
227
+
228
+ def sc_reduce_right[D](
229
+ iterable: Iterable[D],
230
+ f: Callable[[D, D], D],
231
+ start: Callable[[D], bool] = (lambda d: False),
232
+ stop: Callable[[D], bool] = (lambda d: False),
233
+ include_start: bool = True,
234
+ include_stop: bool = True,
235
+ ) -> tuple[MayBe[D], Iterator[D]]:
236
+ """Short circuit version of a right reduce.
237
+
238
+ Useful for infinite and non-reversible iterables.
239
+
240
+ Behavior for default arguments will
241
+
242
+ - right reduce finite iterable
243
+ - start folding at end (of a possibly infinite iterable)
244
+ - continue reducing right until beginning
245
+
246
+ :param iterable: iterable to be reduced from the right
247
+ :param f: two parameter function, second parameter for accumulated value
248
+ :param start: delay starting the fold until it returns true
249
+ :param stop: prematurely stop the fold when it returns true
250
+ :param include_start: if true, include fold starting value in fold
251
+ :param include_stop: if true, include stopping value in fold
252
+ :return: tuple of a MayBe of the folded value and iterator of remaining iterables
253
+
254
+ """
255
+ it_start, it_rest = take_while_split(iterable, negate(start))
256
+ list1 = list(it_start)
257
+ if include_start:
258
+ try:
259
+ begin = next(it_rest)
260
+ except StopIteration:
261
+ pass
262
+ else:
263
+ list1.append(begin)
264
+
265
+ list1.reverse()
266
+ it_reduce, it_stop = take_while_split(list1, negate(stop))
267
+
268
+ mb_reduced = maybe_fold_left(it_reduce, swap(f))
269
+ if include_stop:
270
+ try:
271
+ end = next(it_stop)
272
+ except StopIteration:
273
+ pass
274
+ else:
275
+ if mb_reduced:
276
+ mb_reduced = MayBe(f(end, mb_reduced.get()))
277
+ else:
278
+ mb_reduced = MayBe(end)
279
+
280
+ return (mb_reduced, it_rest)
@@ -0,0 +1,56 @@
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
+ from collections.abc import Callable, Iterable, Iterator
16
+ from typing import cast, Never
17
+ from pythonic_fp.fptools.function import negate, swap
18
+ from pythonic_fp.fptools.maybe import MayBe
19
+ from pythonic_fp.sentinels.novalue import NoValue
20
+ from .drop_take import drop_while, take_while_split
21
+
22
+ __all__ = [
23
+ 'accumulate',
24
+ 'reduce_left',
25
+ 'fold_left',
26
+ 'maybe_fold_left',
27
+ 'sc_reduce_left',
28
+ 'sc_reduce_right',
29
+ ]
30
+
31
+ def accumulate[D, L](
32
+ iterable: Iterable[D], f: Callable[[L, D], L], initial: L | NoValue = NoValue()
33
+ ) -> Iterator[L]: ...
34
+ def reduce_left[D](iterable: Iterable[D], f: Callable[[D, D], D]) -> D | Never: ...
35
+ def fold_left[D, L](
36
+ iterable: Iterable[D], f: Callable[[L, D], L], initial: L
37
+ ) -> L | Never: ...
38
+ def maybe_fold_left[D, L](
39
+ iterable: Iterable[D], f: Callable[[L, D], L], initial: L | NoValue = NoValue()
40
+ ) -> MayBe[L] | Never: ...
41
+ def sc_reduce_left[D](
42
+ iterable: Iterable[D],
43
+ f: Callable[[D, D], D],
44
+ start: Callable[[D], bool] = (lambda d: True),
45
+ stop: Callable[[D], bool] = (lambda d: False),
46
+ include_start: bool = True,
47
+ include_stop: bool = True,
48
+ ) -> tuple[MayBe[D], Iterator[D]]: ...
49
+ def sc_reduce_right[D](
50
+ iterable: Iterable[D],
51
+ f: Callable[[D, D], D],
52
+ start: Callable[[D], bool] = (lambda d: False),
53
+ stop: Callable[[D], bool] = (lambda d: False),
54
+ include_start: bool = True,
55
+ include_stop: bool = True,
56
+ ) -> tuple[MayBe[D], Iterator[D]]: ...