pyochain 0.5.1__py3-none-any.whl → 0.5.2__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.
Potentially problematic release.
This version of pyochain might be problematic. Click here for more details.
- pyochain/_core/_main.py +24 -38
- pyochain/_dict/_exprs.py +1 -0
- pyochain/_dict/_filters.py +1 -0
- pyochain/_dict/_groups.py +1 -2
- pyochain/_dict/_iter.py +13 -4
- pyochain/_dict/_main.py +2 -5
- pyochain/_dict/_process.py +22 -1
- pyochain/_iter/_aggregations.py +1 -0
- pyochain/_iter/_booleans.py +3 -0
- pyochain/_iter/{_groups.py → _dicts.py} +74 -116
- pyochain/_iter/_eager.py +18 -21
- pyochain/_iter/_filters.py +31 -24
- pyochain/_iter/_joins.py +10 -8
- pyochain/_iter/_lists.py +11 -9
- pyochain/_iter/_main.py +296 -51
- pyochain/_iter/_maps.py +28 -26
- pyochain/_iter/_partitions.py +11 -14
- pyochain/_iter/_process.py +25 -31
- pyochain/_iter/_rolling.py +22 -28
- pyochain/_iter/_tuples.py +119 -14
- {pyochain-0.5.1.dist-info → pyochain-0.5.2.dist-info}/METADATA +4 -6
- pyochain-0.5.2.dist-info/RECORD +32 -0
- pyochain/_iter/_constructors.py +0 -155
- pyochain-0.5.1.dist-info/RECORD +0 -33
- {pyochain-0.5.1.dist-info → pyochain-0.5.2.dist-info}/WHEEL +0 -0
pyochain/_iter/_main.py
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
from
|
|
3
|
+
import itertools
|
|
4
|
+
from collections.abc import (
|
|
5
|
+
Callable,
|
|
6
|
+
Generator,
|
|
7
|
+
Iterable,
|
|
8
|
+
Iterator,
|
|
9
|
+
Sequence,
|
|
10
|
+
)
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Concatenate, overload, override
|
|
12
|
+
|
|
13
|
+
import cytoolz as cz
|
|
5
14
|
|
|
6
15
|
from ._aggregations import BaseAgg
|
|
7
16
|
from ._booleans import BaseBool
|
|
8
|
-
from .
|
|
17
|
+
from ._dicts import BaseDict
|
|
9
18
|
from ._eager import BaseEager
|
|
10
19
|
from ._filters import BaseFilter
|
|
11
|
-
from ._groups import BaseGroups
|
|
12
20
|
from ._joins import BaseJoins
|
|
13
21
|
from ._lists import BaseList
|
|
14
22
|
from ._maps import BaseMap
|
|
@@ -21,8 +29,11 @@ if TYPE_CHECKING:
|
|
|
21
29
|
from .._dict import Dict
|
|
22
30
|
|
|
23
31
|
|
|
32
|
+
class CommonMethods[T](BaseAgg[T], BaseEager[T], BaseDict[T]):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
24
36
|
class Iter[T](
|
|
25
|
-
BaseAgg[T],
|
|
26
37
|
BaseBool[T],
|
|
27
38
|
BaseFilter[T],
|
|
28
39
|
BaseProcess[T],
|
|
@@ -32,18 +43,15 @@ class Iter[T](
|
|
|
32
43
|
BaseTuples[T],
|
|
33
44
|
BasePartitions[T],
|
|
34
45
|
BaseJoins[T],
|
|
35
|
-
|
|
36
|
-
BaseEager[T],
|
|
37
|
-
IterConstructors,
|
|
46
|
+
CommonMethods[T],
|
|
38
47
|
):
|
|
39
48
|
"""
|
|
40
|
-
A wrapper around Python's built-in
|
|
49
|
+
A wrapper around Python's built-in Iterators/Generators types, providing a rich set of functional programming tools.
|
|
41
50
|
|
|
42
|
-
It
|
|
51
|
+
It's designed around lazy evaluation, allowing for efficient processing of large datasets.
|
|
43
52
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
It can be constructed from any iterable, including `lists`, `tuples`, `sets`, and `generators`.
|
|
53
|
+
- To instantiate from a lazy Iterator/Generator, simply pass it to the standard constructor.
|
|
54
|
+
- To instantiate from an eager Sequence (like a list or set), use the `from_` class method.
|
|
47
55
|
"""
|
|
48
56
|
|
|
49
57
|
__slots__ = ("_data",)
|
|
@@ -51,8 +59,160 @@ class Iter[T](
|
|
|
51
59
|
def __init__(self, data: Iterator[T] | Generator[T, Any, Any]) -> None:
|
|
52
60
|
self._data = data
|
|
53
61
|
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
@staticmethod
|
|
63
|
+
def from_count(start: int = 0, step: int = 1) -> Iter[int]:
|
|
64
|
+
"""
|
|
65
|
+
Create an infinite iterator of evenly spaced values.
|
|
66
|
+
|
|
67
|
+
**Warning** ⚠️
|
|
68
|
+
This creates an infinite iterator.
|
|
69
|
+
Be sure to use `Iter.take()` or `Iter.slice()` to limit the number of items taken.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
start: Starting value of the sequence. Defaults to 0.
|
|
73
|
+
step: Difference between consecutive values. Defaults to 1.
|
|
74
|
+
Example:
|
|
75
|
+
```python
|
|
76
|
+
>>> import pyochain as pc
|
|
77
|
+
>>> pc.Iter.from_count(10, 2).take(3).into(list)
|
|
78
|
+
[10, 12, 14]
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
return Iter(itertools.count(start, step))
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def from_func[U](func: Callable[[U], U], input: U) -> Iter[U]:
|
|
87
|
+
"""
|
|
88
|
+
Create an infinite iterator by repeatedly applying a function on an original input.
|
|
89
|
+
|
|
90
|
+
**Warning** ⚠️
|
|
91
|
+
This creates an infinite iterator.
|
|
92
|
+
Be sure to use `Iter.take()` or `Iter.slice()` to limit the number of items taken.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
func: Function to apply repeatedly.
|
|
96
|
+
input: Initial value to start the iteration.
|
|
97
|
+
|
|
98
|
+
Example:
|
|
99
|
+
```python
|
|
100
|
+
>>> import pyochain as pc
|
|
101
|
+
>>> pc.Iter.from_func(lambda x: x + 1, 0).take(3).into(list)
|
|
102
|
+
[0, 1, 2]
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
return Iter(cz.itertoolz.iterate(func, input))
|
|
108
|
+
|
|
109
|
+
@overload
|
|
110
|
+
@staticmethod
|
|
111
|
+
def from_[U](data: Iterable[U]) -> Iter[U]: ...
|
|
112
|
+
@overload
|
|
113
|
+
@staticmethod
|
|
114
|
+
def from_[U](data: U, *more_data: U) -> Iter[U]: ...
|
|
115
|
+
@staticmethod
|
|
116
|
+
def from_[U](data: Iterable[U] | U, *more_data: U) -> Iter[U]:
|
|
117
|
+
"""
|
|
118
|
+
Create an iterator from any Iterable, or from unpacked values.
|
|
119
|
+
|
|
120
|
+
- An Iterable is any object capable of returning its members one at a time, permitting it to be iterated over in a for-loop.
|
|
121
|
+
- An Iterator is an object representing a stream of data; returned by calling `iter()` on an Iterable.
|
|
122
|
+
- Once an Iterator is exhausted, it cannot be reused or reset.
|
|
123
|
+
|
|
124
|
+
If you need to reuse the data, consider collecting it into a list first with `.collect()`.
|
|
125
|
+
|
|
126
|
+
In general, avoid intermediate references when dealing with lazy iterators, and prioritize method chaining instead.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
data: Iterable to convert into an iterator, or a single value.
|
|
130
|
+
more_data: Additional values to include if 'data' is not an Iterable.
|
|
131
|
+
Example:
|
|
132
|
+
```python
|
|
133
|
+
>>> import pyochain as pc
|
|
134
|
+
>>> data: tuple[int, ...] = (1, 2, 3)
|
|
135
|
+
>>> iterator = pc.Iter.from_(data)
|
|
136
|
+
>>> iterator.unwrap().__class__.__name__
|
|
137
|
+
'tuple_iterator'
|
|
138
|
+
>>> mapped = iterator.map(lambda x: x * 2)
|
|
139
|
+
>>> mapped.unwrap().__class__.__name__
|
|
140
|
+
'map'
|
|
141
|
+
>>> mapped.collect(tuple).unwrap()
|
|
142
|
+
(2, 4, 6)
|
|
143
|
+
>>> # iterator is now exhausted
|
|
144
|
+
>>> iterator.collect().unwrap()
|
|
145
|
+
[]
|
|
146
|
+
>>> # Creating from unpacked values
|
|
147
|
+
>>> pc.Iter.from_(1, 2, 3).collect(tuple).unwrap()
|
|
148
|
+
(1, 2, 3)
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
"""
|
|
152
|
+
if cz.itertoolz.isiterable(data):
|
|
153
|
+
return Iter(iter(data))
|
|
154
|
+
else:
|
|
155
|
+
d: Iterable[U] = (data, *more_data) # type: ignore[assignment]
|
|
156
|
+
return Iter(iter(d))
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def unfold[S, V](seed: S, generator: Callable[[S], tuple[V, S] | None]) -> Iter[V]:
|
|
160
|
+
"""
|
|
161
|
+
Create an iterator by repeatedly applying a generator function to an initial state.
|
|
162
|
+
|
|
163
|
+
The `generator` function takes the current state and must return:
|
|
164
|
+
|
|
165
|
+
- A tuple `(value, new_state)` to emit the `value` and continue with the `new_state`.
|
|
166
|
+
- `None` to stop the generation.
|
|
167
|
+
|
|
168
|
+
This is functionally equivalent to a state-based `while` loop.
|
|
169
|
+
|
|
170
|
+
**Warning** ⚠️
|
|
171
|
+
If the `generator` function never returns `None`, it creates an infinite iterator.
|
|
172
|
+
Be sure to use `Iter.take()` or `Iter.slice()` to limit the number of items taken if necessary.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
seed: Initial state for the generator.
|
|
176
|
+
generator: Function that generates the next value and state.
|
|
177
|
+
|
|
178
|
+
Example:
|
|
179
|
+
```python
|
|
180
|
+
>>> import pyochain as pc
|
|
181
|
+
>>> # Example 1: Simple counter up to 5
|
|
182
|
+
>>> def counter_generator(state: int) -> tuple[int, int] | None:
|
|
183
|
+
... if state < 5:
|
|
184
|
+
... return (state * 10, state + 1)
|
|
185
|
+
... return None
|
|
186
|
+
>>> pc.Iter.unfold(seed=0, generator=counter_generator).into(list)
|
|
187
|
+
[0, 10, 20, 30, 40]
|
|
188
|
+
>>> # Example 2: Fibonacci sequence up to 100
|
|
189
|
+
>>> type FibState = tuple[int, int]
|
|
190
|
+
>>> def fib_generator(state: FibState) -> tuple[int, FibState] | None:
|
|
191
|
+
... a, b = state
|
|
192
|
+
... if a > 100:
|
|
193
|
+
... return None
|
|
194
|
+
... return (a, (b, a + b))
|
|
195
|
+
>>> pc.Iter.unfold(seed=(0, 1), generator=fib_generator).into(list)
|
|
196
|
+
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
|
|
197
|
+
>>> # Example 3: Infinite iterator (requires take())
|
|
198
|
+
>>> pc.Iter.unfold(seed=1, generator=lambda s: (s, s * 2)).take(5).into(list)
|
|
199
|
+
[1, 2, 4, 8, 16]
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
"""
|
|
203
|
+
from ._main import Iter
|
|
204
|
+
|
|
205
|
+
def _unfold() -> Iterator[V]:
|
|
206
|
+
current_seed: S = seed
|
|
207
|
+
while True:
|
|
208
|
+
result: tuple[V, S] | None = generator(current_seed)
|
|
209
|
+
if result is None:
|
|
210
|
+
break
|
|
211
|
+
value, next_seed = result
|
|
212
|
+
yield value
|
|
213
|
+
current_seed = next_seed
|
|
214
|
+
|
|
215
|
+
return Iter(_unfold())
|
|
56
216
|
|
|
57
217
|
def itr[**P, R, U: Iterable[Any]](
|
|
58
218
|
self: Iter[U],
|
|
@@ -86,9 +246,9 @@ class Iter[T](
|
|
|
86
246
|
"""
|
|
87
247
|
|
|
88
248
|
def _itr(data: Iterable[U]) -> Generator[R, None, None]:
|
|
89
|
-
return (func(Iter
|
|
249
|
+
return (func(Iter(iter(x)), *args, **kwargs) for x in data)
|
|
90
250
|
|
|
91
|
-
return self.
|
|
251
|
+
return self._lazy(_itr)
|
|
92
252
|
|
|
93
253
|
def struct[**P, R, K, V](
|
|
94
254
|
self: Iter[dict[K, V]],
|
|
@@ -100,6 +260,7 @@ class Iter[T](
|
|
|
100
260
|
Apply a function to each element after wrapping it in a Dict.
|
|
101
261
|
|
|
102
262
|
This is a convenience method for the common pattern of mapping a function over an iterable of dictionaries.
|
|
263
|
+
|
|
103
264
|
Args:
|
|
104
265
|
func: Function to apply to each wrapped dictionary.
|
|
105
266
|
*args: Positional arguments to pass to the function.
|
|
@@ -151,74 +312,158 @@ class Iter[T](
|
|
|
151
312
|
def _struct(data: Iterable[dict[K, V]]) -> Generator[R, None, None]:
|
|
152
313
|
return (func(Dict(x), *args, **kwargs) for x in data)
|
|
153
314
|
|
|
154
|
-
return self.
|
|
315
|
+
return self._lazy(_struct)
|
|
155
316
|
|
|
156
|
-
def
|
|
317
|
+
def apply[**P, R](
|
|
318
|
+
self,
|
|
319
|
+
func: Callable[Concatenate[Iterable[T], P], Iterator[R]],
|
|
320
|
+
*args: P.args,
|
|
321
|
+
**kwargs: P.kwargs,
|
|
322
|
+
) -> Iter[R]:
|
|
157
323
|
"""
|
|
158
|
-
|
|
324
|
+
Apply a function to the underlying Iterator and return a new Iter instance.
|
|
325
|
+
|
|
326
|
+
Allow to pass user defined functions that transform the iterable while retaining the Iter wrapper.
|
|
159
327
|
|
|
160
328
|
Args:
|
|
161
|
-
|
|
329
|
+
func: Function to apply to the underlying iterable.
|
|
330
|
+
*args: Positional arguments to pass to the function.
|
|
331
|
+
**kwargs: Keyword arguments to pass to the function.
|
|
332
|
+
|
|
162
333
|
Example:
|
|
163
334
|
```python
|
|
164
335
|
>>> import pyochain as pc
|
|
165
|
-
>>>
|
|
166
|
-
|
|
167
|
-
>>> pc.Iter.from_(
|
|
168
|
-
|
|
169
|
-
>>> # This is equivalent to:
|
|
170
|
-
>>> pc.Iter.from_(keys).zip(values).pipe(
|
|
171
|
-
... lambda x: pc.Dict(x.into(dict)).unwrap()
|
|
172
|
-
... )
|
|
173
|
-
{'a': 1, 'b': 2, 'c': 3}
|
|
336
|
+
>>> def double(data: Iterable[int]) -> Iterator[int]:
|
|
337
|
+
... return (x * 2 for x in data)
|
|
338
|
+
>>> pc.Iter.from_([1, 2, 3]).apply(double).into(list)
|
|
339
|
+
[2, 4, 6]
|
|
174
340
|
|
|
175
341
|
```
|
|
176
342
|
"""
|
|
177
|
-
|
|
343
|
+
return self._lazy(func, *args, **kwargs)
|
|
178
344
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def with_values[V](self, values: Iterable[V]) -> Dict[T, V]:
|
|
345
|
+
def collect(self, factory: Callable[[Iterable[T]], Sequence[T]] = list) -> Seq[T]:
|
|
182
346
|
"""
|
|
183
|
-
|
|
347
|
+
Collect the elements into a sequence, using the provided factory.
|
|
184
348
|
|
|
185
349
|
Args:
|
|
186
|
-
|
|
350
|
+
factory: A callable that takes an iterable and returns a Sequence. Defaults to list.
|
|
351
|
+
|
|
187
352
|
Example:
|
|
188
353
|
```python
|
|
189
354
|
>>> import pyochain as pc
|
|
190
|
-
>>>
|
|
191
|
-
|
|
192
|
-
>>> pc.Iter.from_(keys).with_values(values).unwrap()
|
|
193
|
-
{1: 'a', 2: 'b', 3: 'c'}
|
|
194
|
-
>>> # This is equivalent to:
|
|
195
|
-
>>> pc.Iter.from_(keys).zip(values).pipe(
|
|
196
|
-
... lambda x: pc.Dict(x.into(dict)).unwrap()
|
|
197
|
-
... )
|
|
198
|
-
{1: 'a', 2: 'b', 3: 'c'}
|
|
355
|
+
>>> pc.Iter.from_(range(5)).collect().unwrap()
|
|
356
|
+
[0, 1, 2, 3, 4]
|
|
199
357
|
|
|
200
358
|
```
|
|
201
359
|
"""
|
|
202
|
-
|
|
360
|
+
return self._eager(factory)
|
|
361
|
+
|
|
362
|
+
@override
|
|
363
|
+
def unwrap(self) -> Iterator[T]:
|
|
364
|
+
"""
|
|
365
|
+
Unwrap and return the underlying Iterator.
|
|
203
366
|
|
|
204
|
-
|
|
367
|
+
```python
|
|
368
|
+
>>> import pyochain as pc
|
|
369
|
+
>>> iterator = pc.Iter.from_([1, 2, 3])
|
|
370
|
+
>>> unwrapped = iterator.unwrap()
|
|
371
|
+
>>> list(unwrapped)
|
|
372
|
+
[1, 2, 3]
|
|
205
373
|
|
|
374
|
+
```
|
|
375
|
+
"""
|
|
376
|
+
return self._data # type: ignore[return-value]
|
|
206
377
|
|
|
207
|
-
|
|
378
|
+
|
|
379
|
+
class Seq[T](CommonMethods[T]):
|
|
208
380
|
"""
|
|
209
|
-
pyochain.Seq represent an in memory
|
|
381
|
+
pyochain.Seq represent an in memory Sequence.
|
|
210
382
|
|
|
211
383
|
Provides a subset of pyochain.Iter methods with eager evaluation, and is the return type of pyochain.Iter.collect().
|
|
212
384
|
"""
|
|
213
385
|
|
|
214
386
|
__slots__ = ("_data",)
|
|
215
387
|
|
|
216
|
-
def __init__(self, data:
|
|
388
|
+
def __init__(self, data: Sequence[T]) -> None:
|
|
217
389
|
self._data = data
|
|
218
390
|
|
|
391
|
+
@overload
|
|
392
|
+
@staticmethod
|
|
393
|
+
def from_[U](data: Sequence[U]) -> Seq[U]: ...
|
|
394
|
+
@overload
|
|
395
|
+
@staticmethod
|
|
396
|
+
def from_[U](data: U, *more_data: U) -> Seq[U]: ...
|
|
397
|
+
@staticmethod
|
|
398
|
+
def from_[U](data: Sequence[U] | U, *more_data: U) -> Seq[U]:
|
|
399
|
+
"""
|
|
400
|
+
Create a Seq from a Sequence or unpacked values.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
data: Sequence of items or a single item.
|
|
404
|
+
more_data: Additional item to include if 'data' is not a Sequence.
|
|
405
|
+
|
|
406
|
+
Example:
|
|
407
|
+
```python
|
|
408
|
+
>>> import pyochain as pc
|
|
409
|
+
>>> pc.Seq.from_([1, 2, 3]).unwrap()
|
|
410
|
+
[1, 2, 3]
|
|
411
|
+
>>> pc.Seq.from_(1, 2).unwrap()
|
|
412
|
+
(1, 2)
|
|
413
|
+
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
"""
|
|
417
|
+
if cz.itertoolz.isiterable(data):
|
|
418
|
+
return Seq(data)
|
|
419
|
+
else:
|
|
420
|
+
return Seq((data, *more_data))
|
|
421
|
+
|
|
219
422
|
def iter(self) -> Iter[T]:
|
|
220
423
|
"""
|
|
221
424
|
Get an iterator over the sequence.
|
|
222
425
|
Call this to switch to lazy evaluation.
|
|
223
426
|
"""
|
|
224
|
-
return
|
|
427
|
+
return self._lazy(iter)
|
|
428
|
+
|
|
429
|
+
def apply[**P, R](
|
|
430
|
+
self,
|
|
431
|
+
func: Callable[Concatenate[Iterable[T], P], Sequence[R]],
|
|
432
|
+
*args: P.args,
|
|
433
|
+
**kwargs: P.kwargs,
|
|
434
|
+
) -> Seq[R]:
|
|
435
|
+
"""
|
|
436
|
+
Apply a function to the underlying Sequence and return a Seq instance.
|
|
437
|
+
|
|
438
|
+
Allow to pass user defined functions that transform the Sequence while retaining the Seq wrapper.
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
func: Function to apply to the underlying Sequence.
|
|
442
|
+
*args: Positional arguments to pass to the function.
|
|
443
|
+
**kwargs: Keyword arguments to pass to the function.
|
|
444
|
+
|
|
445
|
+
Example:
|
|
446
|
+
```python
|
|
447
|
+
>>> import pyochain as pc
|
|
448
|
+
>>> def double(data: Iterable[int]) -> Sequence[int]:
|
|
449
|
+
... return [x * 2 for x in data]
|
|
450
|
+
>>> pc.Seq([1, 2, 3]).apply(double).into(list)
|
|
451
|
+
[2, 4, 6]
|
|
452
|
+
|
|
453
|
+
```
|
|
454
|
+
"""
|
|
455
|
+
return self._eager(func, *args, **kwargs)
|
|
456
|
+
|
|
457
|
+
@override
|
|
458
|
+
def unwrap(self) -> Sequence[T]:
|
|
459
|
+
"""
|
|
460
|
+
Unwrap and return the underlying Sequence.
|
|
461
|
+
|
|
462
|
+
```python
|
|
463
|
+
>>> import pyochain as pc
|
|
464
|
+
>>> pc.Seq([1, 2, 3]).unwrap()
|
|
465
|
+
[1, 2, 3]
|
|
466
|
+
|
|
467
|
+
```
|
|
468
|
+
"""
|
|
469
|
+
return self._data # type: ignore[return-value]
|
pyochain/_iter/_maps.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import itertools
|
|
4
|
-
from collections.abc import Callable,
|
|
4
|
+
from collections.abc import Callable, Generator, Iterable, Iterator, Mapping, Sequence
|
|
5
5
|
from functools import partial
|
|
6
|
-
from typing import TYPE_CHECKING, Any, Concatenate,
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Concatenate, overload
|
|
7
7
|
|
|
8
8
|
import cytoolz as cz
|
|
9
9
|
import more_itertools as mit
|
|
@@ -20,29 +20,31 @@ class BaseMap[T](IterWrapper[T]):
|
|
|
20
20
|
func: Callable[Concatenate[T, P], Any],
|
|
21
21
|
*args: P.args,
|
|
22
22
|
**kwargs: P.kwargs,
|
|
23
|
-
) ->
|
|
23
|
+
) -> None:
|
|
24
24
|
"""
|
|
25
|
-
|
|
25
|
+
Consume the Iterator by applying a function to each element in the iterable.
|
|
26
26
|
|
|
27
27
|
Args:
|
|
28
28
|
func: Function to apply to each element.
|
|
29
29
|
args: Positional arguments for the function.
|
|
30
30
|
kwargs: Keyword arguments for the function.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Is a terminal operation, and is useful for functions that have side effects, or when you want to force evaluation of a lazy iterable.
|
|
33
33
|
```python
|
|
34
34
|
>>> import pyochain as pc
|
|
35
|
-
>>> pc.Iter.from_([1, 2, 3]).for_each(lambda x: print(x))
|
|
35
|
+
>>> pc.Iter.from_([1, 2, 3]).for_each(lambda x: print(x))
|
|
36
36
|
1
|
|
37
37
|
2
|
|
38
38
|
3
|
|
39
|
-
[]
|
|
40
39
|
|
|
41
40
|
```
|
|
42
41
|
"""
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
|
|
43
|
+
def _for_each(data: Iterable[T]) -> None:
|
|
44
|
+
for v in data:
|
|
45
|
+
func(v, *args, **kwargs)
|
|
46
|
+
|
|
47
|
+
return self.into(_for_each)
|
|
46
48
|
|
|
47
49
|
def map[R](self, func: Callable[[T], R]) -> Iter[R]:
|
|
48
50
|
"""
|
|
@@ -58,7 +60,7 @@ class BaseMap[T](IterWrapper[T]):
|
|
|
58
60
|
|
|
59
61
|
```
|
|
60
62
|
"""
|
|
61
|
-
return self.
|
|
63
|
+
return self._lazy(partial(map, func))
|
|
62
64
|
|
|
63
65
|
@overload
|
|
64
66
|
def flat_map[U, R](
|
|
@@ -94,7 +96,7 @@ class BaseMap[T](IterWrapper[T]):
|
|
|
94
96
|
def _flat_map(data: Iterable[U]) -> map[R]:
|
|
95
97
|
return map(func, itertools.chain.from_iterable(data))
|
|
96
98
|
|
|
97
|
-
return self.
|
|
99
|
+
return self._lazy(_flat_map)
|
|
98
100
|
|
|
99
101
|
def map_star[U: Iterable[Any], R](
|
|
100
102
|
self: IterWrapper[U], func: Callable[..., R]
|
|
@@ -123,11 +125,12 @@ class BaseMap[T](IterWrapper[T]):
|
|
|
123
125
|
['blue-S', 'blue-M', 'red-S', 'red-M']
|
|
124
126
|
|
|
125
127
|
```
|
|
128
|
+
|
|
126
129
|
- Use map_star when the performance matters (it is faster).
|
|
127
130
|
- Use map with unpacking when readability matters (the types can be inferred).
|
|
128
131
|
"""
|
|
129
132
|
|
|
130
|
-
return self.
|
|
133
|
+
return self._lazy(partial(itertools.starmap, func))
|
|
131
134
|
|
|
132
135
|
def map_if[R](
|
|
133
136
|
self,
|
|
@@ -165,7 +168,7 @@ class BaseMap[T](IterWrapper[T]):
|
|
|
165
168
|
|
|
166
169
|
```
|
|
167
170
|
"""
|
|
168
|
-
return self.
|
|
171
|
+
return self._lazy(mit.map_if, predicate, func, func_else=func_else)
|
|
169
172
|
|
|
170
173
|
def map_except[R](
|
|
171
174
|
self, func: Callable[[T], R], *exceptions: type[BaseException]
|
|
@@ -192,17 +195,17 @@ class BaseMap[T](IterWrapper[T]):
|
|
|
192
195
|
def _map_except(data: Iterable[T]) -> Iterator[R]:
|
|
193
196
|
return mit.map_except(func, data, *exceptions)
|
|
194
197
|
|
|
195
|
-
return self.
|
|
198
|
+
return self._lazy(_map_except)
|
|
196
199
|
|
|
197
200
|
def repeat(
|
|
198
|
-
self, n: int, factory: Callable[[Iterable[T]],
|
|
201
|
+
self, n: int, factory: Callable[[Iterable[T]], Sequence[T]] = tuple
|
|
199
202
|
) -> Iter[Iterable[T]]:
|
|
200
203
|
"""
|
|
201
|
-
Repeat the entire iterable n times (as elements)
|
|
204
|
+
Repeat the entire iterable n times (as elements).
|
|
202
205
|
|
|
203
206
|
Args:
|
|
204
207
|
n: Number of repetitions.
|
|
205
|
-
factory: Factory to create the repeated
|
|
208
|
+
factory: Factory to create the repeated Sequence (default: tuple).
|
|
206
209
|
|
|
207
210
|
```python
|
|
208
211
|
>>> import pyochain as pc
|
|
@@ -217,7 +220,7 @@ class BaseMap[T](IterWrapper[T]):
|
|
|
217
220
|
def _repeat(data: Iterable[T]) -> Iterator[Iterable[T]]:
|
|
218
221
|
return itertools.repeat(factory(data), n)
|
|
219
222
|
|
|
220
|
-
return self.
|
|
223
|
+
return self._lazy(_repeat)
|
|
221
224
|
|
|
222
225
|
@overload
|
|
223
226
|
def repeat_last(self, default: T) -> Iter[T]: ...
|
|
@@ -247,7 +250,7 @@ class BaseMap[T](IterWrapper[T]):
|
|
|
247
250
|
|
|
248
251
|
```
|
|
249
252
|
"""
|
|
250
|
-
return self.
|
|
253
|
+
return self._lazy(mit.repeat_last, default)
|
|
251
254
|
|
|
252
255
|
def ichunked(self, n: int) -> Iter[Iterator[T]]:
|
|
253
256
|
"""
|
|
@@ -275,7 +278,7 @@ class BaseMap[T](IterWrapper[T]):
|
|
|
275
278
|
|
|
276
279
|
```
|
|
277
280
|
"""
|
|
278
|
-
return self.
|
|
281
|
+
return self._lazy(mit.ichunked, n)
|
|
279
282
|
|
|
280
283
|
@overload
|
|
281
284
|
def flatten[U](
|
|
@@ -297,7 +300,7 @@ class BaseMap[T](IterWrapper[T]):
|
|
|
297
300
|
|
|
298
301
|
```
|
|
299
302
|
"""
|
|
300
|
-
return self.
|
|
303
|
+
return self._lazy(itertools.chain.from_iterable)
|
|
301
304
|
|
|
302
305
|
def pluck[U: Mapping[Any, Any]](
|
|
303
306
|
self: IterWrapper[U], *keys: str | int
|
|
@@ -333,17 +336,16 @@ class BaseMap[T](IterWrapper[T]):
|
|
|
333
336
|
"""
|
|
334
337
|
|
|
335
338
|
getter = partial(cz.dicttoolz.get_in, keys)
|
|
336
|
-
return self.
|
|
339
|
+
return self._lazy(partial(map, getter))
|
|
337
340
|
|
|
338
341
|
def round[U: float | int](
|
|
339
342
|
self: IterWrapper[U], ndigits: int | None = None
|
|
340
343
|
) -> Iter[float]:
|
|
341
344
|
"""
|
|
342
|
-
Round each element in the iterable to the given number of decimal places
|
|
345
|
+
Round each element in the iterable to the given number of decimal places.
|
|
343
346
|
|
|
344
347
|
Args:
|
|
345
348
|
ndigits: Number of decimal places to round to.
|
|
346
|
-
|
|
347
349
|
```python
|
|
348
350
|
>>> import pyochain as pc
|
|
349
351
|
>>> pc.Iter.from_([1.2345, 2.3456, 3.4567]).round(2).into(list)
|
|
@@ -355,4 +357,4 @@ class BaseMap[T](IterWrapper[T]):
|
|
|
355
357
|
def _round(data: Iterable[U]) -> Generator[float | int, None, None]:
|
|
356
358
|
return (round(x, ndigits) for x in data)
|
|
357
359
|
|
|
358
|
-
return self.
|
|
360
|
+
return self._lazy(_round)
|