You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Heavily inspired by rust's [Iterator trait](https://doc.rust-lang.org/std/iter/trait.Iterator.html),
4
-
with added pythonicness (such as `starmap`)
3
+
`itr` is a powerful Python library that wraps iterators, iterables, and generators, providing a Rust-inspired `Iterator` trait experience with added Pythonic conveniences. It enables developers to build complex data processing pipelines with a fluent, chainable, and lazy API. In most cases, it simply wraps itertools in syntactic sugar.
5
4
5
+
Heavily inspired by Rust's [Iterator trait](https://doc.rust-lang.org/std/iter/trait.Iterator.html), `itr` offers a familiar and robust pattern for sequence manipulation.
6
6
7
-
### Functionality
7
+
##Key Features & Why `Itr`?
8
8
9
-
`Itr` wraps python Iterators, Iterables and Generators providing the ability to arbitrarily chain (and lazily evaluate) methods, e.g.
9
+
***Fluent & Chainable API:** Write expressive, left-to-right sequences of operations on your data.
10
+
***Lazy Evaluation:** Operations are only performed when their results are needed, improving efficiency for large or infinite sequences.
11
+
***Rust-Inspired Patterns:** Bring the power and clarity of Rust's `Iterator` design to your Python projects.
12
+
***Pythonic Additions:** Includes convenient methods like `starmap` that integrate smoothly with Python's ecosystem.
13
+
***Simplifies Complex Logic:** Often provides a more readable and concise alternative to nested `itertools` or built-in functions.
14
+
15
+
## Quick Start & Core Functionality
16
+
17
+
`Itr` wraps Python Iterators, Iterables, and Generators, allowing you to arbitrarily chain methods for data transformation.
10
18
11
19
```py
12
20
>>>from itr import Itr
21
+
>>># Process a range of numbers with a concise, chainable syntax
13
22
>>> Itr(range(100)).rev().step_by(4).skip(10).map(lambdax: x * x).filter(lambdax: x %3==0).for_each(print)
14
-
2304
15
-
1296
16
-
576
17
-
144
18
-
0
19
-
>>>
23
+
2601
24
+
1521
25
+
729
26
+
225
27
+
9
28
+
20
29
```
21
30
22
-
For reference, the equivalent expression using builtins and `itertools`:
31
+
For reference, the equivalent expression using built-ins and `itertools` can be significantly less readable:
23
32
24
-
```py
25
-
fromiteroolsimport islice
33
+
```python
34
+
fromitertoolsimport islice
26
35
27
36
for item infilter(
28
37
lambdax: x %3==0,
29
38
map(lambdax: x * x, islice(islice(reversed(range(100)), None, None, 4), 10, None)),
30
39
):
31
40
print(item)
41
+
32
42
```
33
43
44
+
## How `Itr` Works: Lazy vs. Eager
45
+
46
+
Most `Itr` methods are **lazy transformations**, meaning they return a new `Itr` instance without immediately processing any data. This allows for arbitrary chaining and efficient memory usage, as items are only processed as they are requested. In many cases, `Itr` acts as a convenient wrapper around `itertools`, enabling this left-to-right chaining syntax.
47
+
48
+
However, some methods are **eager consumers**. These methods iterate over and consume the underlying data, returning concrete values, collections, or aggregates. Examples include:
In many cases the `Itr` simply provides a wrapper around `functools` enabling chaining of operations in a left-to-right syntax.
52
+
### Important Considerations
36
53
37
-
Whilst many methods return either `self` or another instance/instances of `Itr` (thus allowing for chaining and lazy evaluation), some methods consume and return actual values (such as `collect`, `next`, `next_chunk`) or aggregates (such as `count`,`reduce`,`max`) from the iterable (i.e. eager evaluation). Note that:
54
+
When working with `Itr`, keep these points in mind:
38
55
39
-
-iteratorscan only be consumed once (use `copy`, `cycle` or `repeat` as necessary)
40
-
- it's not possible to rewind, but the next value in the sequence can be "previewed" using the `peek` method. Doing this a lot is unlikely to be efficent though, as it copies the iterator.
41
-
- be wary of open-ended iterators, eager evaluation can lead to infinite loops.
56
+
***Single-Pass Iterators:** Like all Python iterators, `Itr` instances (and their underlying iterators) can generally only be consumed once. If you need to process the same sequence multiple times, use methods like `copy()`, `cycle()`, or `repeat()` as necessary.
57
+
***No Rewinding:** It's not possible to rewind an `Itr` to an earlier state. You can "preview" the next value using the `peek()` method, but be aware that `peek()` often copies the iterator internally, which can be inefficient if used excessively.
58
+
***Infinite Iterators:** Be cautious with open-ended iterators (e.g., those from `itertools.count()` or custom generators). Eager evaluation methods (like `collect()`, `count()`, `reduce()`) will attempt to consume the entire sequence, potentially leading to infinite loops or out-of-memory errors if applied to an infinite source.
42
59
43
-
Methods implemented (see the docstrings for more details):
60
+
## API Reference
61
+
62
+
`Itr` provides a comprehensive set of methods for various iterable operations. For a complete list of methods and their detailed descriptions, please refer to the [full documentation](autodoc.md).
63
+
64
+
*Note: The detailed list of methods in `autodoc.md` is automatically generated by [introspect.py], where `Itr` introspects itself.*
65
+
66
+
```py introspect.py
67
+
from operator import add
68
+
69
+
from itr import Itr
70
+
71
+
# use Itr to introspect itself for methods and their docstrings
72
+
methods = (
73
+
Itr(dir(Itr))
74
+
.filter(lambdam: m in ("__init__", "__iter__", "__next__") ornot m.startswith("_"))
By default, the `collect()` method returns a `tuple`, but this can be changed to `list`, `set` or `dict`. Here's how to group a words by their length into a dictionary:
142
+
While `Itr` methods typically return another `Itr` instance, the `collect()` method allows you to materialize the results into various Python collections: `tuple` (default), `list`, `set`, or `dict`.
95
143
96
-
```py
144
+
Here's how to group words by their length into a dictionary:
For reference, an equivalent using `itertools` directly:
154
+
103
155
```py
156
+
>>>import itertools
104
157
>>> {k: tuple(v) for k, v in itertools.groupby(("apple", "banana", "carrot"), key=len)}
158
+
{5: ('apple',), 6: ('banana', 'carrot')}
159
+
105
160
```
106
161
162
+
*Note: Using `collect(dict)` requires an iterable that produces 2-tuples (key-value pairs).*
107
163
108
-
NB using `dict` requires an iterable that produces 2-tuples.
164
+
### More examples
109
165
110
-
### Composition
166
+
More examples can be found [here](./doc/examples.md).
111
167
112
-
Numerical differentiation and integration:
113
168
114
-
```py
169
+
170
+
171
+
172
+
Beyond basic transformations, `Itr` enables powerful composition of operations, making complex data processing more manageable. This example demonstrates numerical differentiation and integration using `Itr`.
173
+
174
+
```python
115
175
from itr import Itr
116
176
import numpy as np
177
+
from operator import add
117
178
118
179
x = Itr(np.linspace(0, 1, 11))
119
-
#need a copy as the original x is consumed in the zip method
180
+
#Need a copy of 'x' as the original 'x' is consumed in the zip method.
120
181
xy = x.copy().zip(x.map(lambdaxi: xi * xi /2))
121
182
122
-
defdiff1central(xy):
123
-
(x0, y0), (x1, _y1), (x2, y2) = xy
183
+
# Numerical differentiation (central difference)
184
+
defdiff1central(xy_segment):
185
+
# Unpack the 3-element rolling window: (x0, y0), (x1, y1_ignored), (x2, y2)
186
+
(x0, y0), (x1, _y1), (x2, y2) = xy_segment
124
187
return x1, (y2 - y0) / (x2 - x0)
125
188
189
+
print("Numerical Derivatives:")
126
190
print(
127
-
xy.copy() #need a copy as reusing xy below
191
+
xy.copy() #Need a copy as xy will be reused below for integration
128
192
.rolling(3)
129
-
.map(diff1central) # x, dy/dx pairs
193
+
.map(diff1central) #Produces (x, dy/dx) pairs
130
194
.collect()
131
195
)
132
196
133
-
#similarly, the definite integral x**3/6 -> using the trapezoidal rule over 0,1 = 1/6
134
-
deftrapz(sum, xy):
135
-
(x0, y0), (x1, y1) = xy
136
-
returnsum+0.5* (y0 +y1) * (x1 - x0)
137
-
138
-
print(xy.pairwise().fold(0, trapz))
197
+
#Numerical integration (trapezoidal rule)
198
+
# Integrates x**2 / 2, resulting in x**3 / 6. Over 0,1 this is 1/6.
199
+
deftrapz_accumulate(current_sum, xy_pair):
200
+
# Unpack (x0, y0) and (x1, y1) from the pairwise iteration
201
+
(x0, y0), (x1, y1) = xy_pair
202
+
return current_sum +0.5* (y0 + y1) * (x1 - x0)
139
203
140
-
# ...or an alternative implementation using starmap and two levels of flattening
204
+
print("\nNumerical Integral (Trapezoidal Rule - Fold):")
0 commit comments