pyochain 0.5.3__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,404 @@
1
+ from __future__ import annotations
2
+
3
+ import itertools
4
+ from collections.abc import Callable, Generator, Iterable, Iterator
5
+ from typing import TYPE_CHECKING, Any, TypeIs, overload
6
+
7
+ import cytoolz as cz
8
+ import more_itertools as mit
9
+
10
+ from .._core import IterWrapper
11
+
12
+ if TYPE_CHECKING:
13
+ from ._main import Iter
14
+
15
+
16
+ class BaseJoins[T](IterWrapper[T]):
17
+ @overload
18
+ def zip[T1](
19
+ self, iter1: Iterable[T1], /, *, strict: bool = ...
20
+ ) -> Iter[tuple[T, T1]]: ...
21
+ @overload
22
+ def zip[T1, T2](
23
+ self,
24
+ iter1: Iterable[T1],
25
+ iter2: Iterable[T2],
26
+ /,
27
+ *,
28
+ strict: bool = ...,
29
+ ) -> Iter[tuple[T, T1, T2]]: ...
30
+ @overload
31
+ def zip[T1, T2, T3](
32
+ self,
33
+ iter1: Iterable[T1],
34
+ iter2: Iterable[T2],
35
+ iter3: Iterable[T3],
36
+ /,
37
+ *,
38
+ strict: bool = ...,
39
+ ) -> Iter[tuple[T, T1, T2, T3]]: ...
40
+ @overload
41
+ def zip[T1, T2, T3, T4](
42
+ self,
43
+ iter1: Iterable[T1],
44
+ iter2: Iterable[T2],
45
+ iter3: Iterable[T3],
46
+ iter4: Iterable[T4],
47
+ /,
48
+ *,
49
+ strict: bool = ...,
50
+ ) -> Iter[tuple[T, T1, T2, T3, T4]]: ...
51
+ def zip(
52
+ self, *others: Iterable[Any], strict: bool = False
53
+ ) -> Iter[tuple[Any, ...]]:
54
+ """
55
+ Zip with other iterables, optionally strict.
56
+
57
+ Args:
58
+ *others: Other iterables to zip with.
59
+ strict: Whether to enforce equal lengths of iterables. Defaults to False.
60
+ Example:
61
+ ```python
62
+ >>> import pyochain as pc
63
+ >>> pc.Iter.from_([1, 2]).zip([10, 20]).into(list)
64
+ [(1, 10), (2, 20)]
65
+ >>> pc.Iter.from_(["a", "b"]).zip([1, 2, 3]).into(list)
66
+ [('a', 1), ('b', 2)]
67
+
68
+ ```
69
+ """
70
+ return self._lazy(zip, *others, strict=strict)
71
+
72
+ def zip_offset[U](
73
+ self,
74
+ *others: Iterable[T],
75
+ offsets: list[int],
76
+ longest: bool = False,
77
+ fillvalue: U = None,
78
+ ) -> Iter[tuple[T | U, ...]]:
79
+ """
80
+ Zip the input iterables together, but offset the i-th iterable by the i-th item in offsets.
81
+
82
+ Args:
83
+ *others: Other iterables to zip with.
84
+ offsets: List of integers specifying the offsets for each iterable.
85
+ longest: Whether to continue until the longest iterable is exhausted. Defaults to False.
86
+ fillvalue: Value to use for missing elements. Defaults to None.
87
+ Example:
88
+ ```python
89
+ >>> import pyochain as pc
90
+ >>> data = pc.Seq("0123")
91
+ >>> data.iter().zip_offset("abcdef", offsets=(0, 1)).into(list)
92
+ [('0', 'b'), ('1', 'c'), ('2', 'd'), ('3', 'e')]
93
+
94
+ ```
95
+ This can be used as a lightweight alternative to SciPy or pandas to analyze data sets in which some series have a lead or lag relationship.
96
+
97
+ By default, the sequence will end when the shortest iterable is exhausted.
98
+
99
+ To continue until the longest iterable is exhausted, set longest to True.
100
+ ```python
101
+ >>> data.iter().zip_offset("abcdef", offsets=(0, 1), longest=True).into(list)
102
+ [('0', 'b'), ('1', 'c'), ('2', 'd'), ('3', 'e'), (None, 'f')]
103
+
104
+ ```
105
+ """
106
+
107
+ def _zip_offset(data: Iterable[T]) -> Iterator[tuple[T | U, ...]]:
108
+ return mit.zip_offset(
109
+ data,
110
+ *others,
111
+ offsets=offsets,
112
+ longest=longest,
113
+ fillvalue=fillvalue,
114
+ )
115
+
116
+ return self._lazy(_zip_offset)
117
+
118
+ @overload
119
+ def zip_broadcast[T1](
120
+ self,
121
+ iter1: Iterable[T1],
122
+ /,
123
+ *,
124
+ strict: bool = False,
125
+ ) -> Iter[tuple[T, T1]]: ...
126
+ @overload
127
+ def zip_broadcast[T1, T2](
128
+ self,
129
+ iter1: Iterable[T1],
130
+ iter2: Iterable[T2],
131
+ /,
132
+ *,
133
+ strict: bool = False,
134
+ ) -> Iter[tuple[T, T1, T2]]: ...
135
+ @overload
136
+ def zip_broadcast[T1, T2, T3](
137
+ self,
138
+ iter1: Iterable[T1],
139
+ iter2: Iterable[T2],
140
+ iter3: Iterable[T3],
141
+ /,
142
+ *,
143
+ strict: bool = False,
144
+ ) -> Iter[tuple[T, T1, T2, T3]]: ...
145
+ @overload
146
+ def zip_broadcast[T1, T2, T3, T4](
147
+ self,
148
+ iter1: Iterable[T1],
149
+ iter2: Iterable[T2],
150
+ iter3: Iterable[T3],
151
+ iter4: Iterable[T4],
152
+ /,
153
+ *,
154
+ strict: bool = False,
155
+ ) -> Iter[tuple[T, T1, T2, T3, T4]]: ...
156
+ def zip_broadcast(
157
+ self, *others: Iterable[Any], strict: bool = False
158
+ ) -> Iter[tuple[Any, ...]]:
159
+ """
160
+ Version of zip that "broadcasts" any scalar (i.e., non-iterable) items into output tuples.
161
+
162
+ `str` and `bytes` are not treated as iterables.
163
+
164
+ If the strict keyword argument is True, then UnequalIterablesError will be raised if any of the iterables have different lengths.
165
+
166
+ Args:
167
+ *others: Other iterables or scalars to zip with.
168
+ strict: Whether to enforce equal lengths of iterables. Defaults to False.
169
+ Example:
170
+ ```python
171
+ >>> import pyochain as pc
172
+ >>> data = pc.Iter.from_([1, 2, 3])
173
+ >>> other = ["a", "b", "c"]
174
+ >>> scalar = "_"
175
+ >>> data.zip_broadcast(other, scalar).into(list)
176
+ [(1, 'a', '_'), (2, 'b', '_'), (3, 'c', '_')]
177
+
178
+ ```
179
+ """
180
+
181
+ def _zip_broadcast(
182
+ *objects: Iterable[Any],
183
+ ) -> Generator[tuple[Iterable[Any], ...] | tuple[object, ...], Any, None]:
184
+ """from more_itertools.zip_broadcast"""
185
+
186
+ def is_scalar(obj: Any) -> TypeIs[object]:
187
+ if isinstance(obj, (str, bytes)):
188
+ return True
189
+ return cz.itertoolz.isiterable(obj) is False
190
+
191
+ size = len(objects)
192
+ if not size:
193
+ return
194
+
195
+ new_item: list[object] = [None] * size
196
+ iterables: list[Iterator[Any]] = []
197
+ iterable_positions: list[int] = []
198
+ for i, obj in enumerate(objects):
199
+ if is_scalar(obj):
200
+ new_item[i] = obj
201
+ else:
202
+ iterables.append(iter(obj))
203
+ iterable_positions.append(i)
204
+
205
+ if not iterables:
206
+ yield tuple(objects)
207
+ return
208
+
209
+ zipper = mit.zip_equal if strict else zip
210
+ for item in zipper(*iterables):
211
+ for i, new_item[i] in zip(iterable_positions, item):
212
+ pass
213
+ yield tuple(new_item)
214
+
215
+ return self._lazy(_zip_broadcast, *others)
216
+
217
+ @overload
218
+ def zip_equal(self) -> Iter[tuple[T]]: ...
219
+ @overload
220
+ def zip_equal[T2](self, __iter2: Iterable[T2]) -> Iter[tuple[T, T2]]: ...
221
+ @overload
222
+ def zip_equal[T2, T3](
223
+ self, __iter2: Iterable[T2], __iter3: Iterable[T3]
224
+ ) -> Iter[tuple[T, T2, T3]]: ...
225
+ @overload
226
+ def zip_equal[T2, T3, T4](
227
+ self,
228
+ __iter2: Iterable[T2],
229
+ __iter3: Iterable[T3],
230
+ __iter4: Iterable[T4],
231
+ ) -> Iter[tuple[T, T2, T3, T4]]: ...
232
+ @overload
233
+ def zip_equal[T2, T3, T4, T5](
234
+ self,
235
+ __iter2: Iterable[T2],
236
+ __iter3: Iterable[T3],
237
+ __iter4: Iterable[T4],
238
+ __iter5: Iterable[T5],
239
+ ) -> Iter[tuple[T, T2, T3, T4, T5]]: ...
240
+ def zip_equal(self, *others: Iterable[Any]) -> Iter[tuple[Any, ...]]:
241
+ """
242
+ `zip` the input *iterables* together but raise `UnequalIterablesError` if they aren't all the same length.
243
+
244
+ Args:
245
+ *others: Other iterables to zip with.
246
+ Example:
247
+ ```python
248
+ >>> import pyochain as pc
249
+ >>> pc.Iter.from_(range(3)).zip_equal("abc").into(list)
250
+ [(0, 'a'), (1, 'b'), (2, 'c')]
251
+ >>> pc.Iter.from_(range(3)).zip_equal("abcd").into(list)
252
+ ... # doctest: +IGNORE_EXCEPTION_DETAIL
253
+ Traceback (most recent call last):
254
+ ...
255
+ more_itertools.more.UnequalIterablesError: Iterables have different
256
+ lengths
257
+
258
+ ```
259
+ """
260
+
261
+ def _zip_equal(data: Iterable[T]) -> Iterator[tuple[Any, ...]]:
262
+ return mit.zip_equal(data, *others)
263
+
264
+ return self._lazy(_zip_equal)
265
+
266
+ def zip_longest[U](
267
+ self, *others: Iterable[T], fill_value: U = None
268
+ ) -> Iter[tuple[U | T, ...]]:
269
+ """
270
+ Zip with other iterables, filling missing values.
271
+
272
+ Args:
273
+ *others: Other iterables to zip with.
274
+ fill_value: Value to use for missing elements. Defaults to None.
275
+ Example:
276
+ ```python
277
+ >>> import pyochain as pc
278
+ >>> pc.Iter.from_([1, 2]).zip_longest([10], fill_value=0).into(list)
279
+ [(1, 10), (2, 0)]
280
+
281
+ ```
282
+ """
283
+ return self._lazy(itertools.zip_longest, *others, fillvalue=fill_value)
284
+
285
+ @overload
286
+ def product(self) -> Iter[tuple[T]]: ...
287
+ @overload
288
+ def product[T1](self, iter1: Iterable[T1], /) -> Iter[tuple[T, T1]]: ...
289
+ @overload
290
+ def product[T1, T2](
291
+ self, iter1: Iterable[T1], iter2: Iterable[T2], /
292
+ ) -> Iter[tuple[T, T1, T2]]: ...
293
+ @overload
294
+ def product[T1, T2, T3](
295
+ self, iter1: Iterable[T1], iter2: Iterable[T2], iter3: Iterable[T3], /
296
+ ) -> Iter[tuple[T, T1, T2, T3]]: ...
297
+ @overload
298
+ def product[T1, T2, T3, T4](
299
+ self,
300
+ iter1: Iterable[T1],
301
+ iter2: Iterable[T2],
302
+ iter3: Iterable[T3],
303
+ iter4: Iterable[T4],
304
+ /,
305
+ ) -> Iter[tuple[T, T1, T2, T3, T4]]: ...
306
+
307
+ def product(self, *others: Iterable[Any]) -> Iter[tuple[Any, ...]]:
308
+ """
309
+ Computes the Cartesian product with another iterable.
310
+ This is the declarative equivalent of nested for-loops.
311
+
312
+ It pairs every element from the source iterable with every element from the
313
+ other iterable.
314
+
315
+ Args:
316
+ *others: Other iterables to compute the Cartesian product with.
317
+ Example:
318
+ ```python
319
+ >>> import pyochain as pc
320
+ >>> colors = pc.Iter.from_(["blue", "red"])
321
+ >>> sizes = ["S", "M"]
322
+ >>> colors.product(sizes).into(list)
323
+ [('blue', 'S'), ('blue', 'M'), ('red', 'S'), ('red', 'M')]
324
+
325
+ ```
326
+ """
327
+ return self._lazy(itertools.product, *others)
328
+
329
+ def diff_at(
330
+ self,
331
+ *others: Iterable[T],
332
+ default: T | None = None,
333
+ key: Callable[[T], Any] | None = None,
334
+ ) -> Iter[tuple[T, ...]]:
335
+ """
336
+ Return those items that differ between iterables.
337
+ Each output item is a tuple where the i-th element is from the i-th input iterable.
338
+
339
+ If an input iterable is exhausted before others, then the corresponding output items will be filled with *default*.
340
+
341
+ Args:
342
+ *others: Other iterables to compare with.
343
+ default: Value to use for missing elements. Defaults to None.
344
+ key: Function to apply to each item for comparison. Defaults to None.
345
+ Example:
346
+ ```python
347
+ >>> import pyochain as pc
348
+ >>> data = pc.Seq([1, 2, 3])
349
+ >>> data.iter().diff_at([1, 2, 10, 100], default=None).into(list)
350
+ [(3, 10), (None, 100)]
351
+ >>> data.iter().diff_at([1, 2, 10, 100, 2, 6, 7], default=0).into(list)
352
+ [(3, 10), (0, 100), (0, 2), (0, 6), (0, 7)]
353
+
354
+ A key function may also be applied to each item to use during comparisons:
355
+ ```python
356
+ >>> import pyochain as pc
357
+ >>> pc.Iter.from_(["apples", "bananas"]).diff_at(
358
+ ... ["Apples", "Oranges"], key=str.lower
359
+ ... ).into(list)
360
+ [('bananas', 'Oranges')]
361
+
362
+ ```
363
+ """
364
+ return self._lazy(cz.itertoolz.diff, *others, default=default, key=key)
365
+
366
+ def join[R, K](
367
+ self,
368
+ other: Iterable[R],
369
+ left_on: Callable[[T], K],
370
+ right_on: Callable[[R], K],
371
+ left_default: T | None = None,
372
+ right_default: R | None = None,
373
+ ) -> Iter[tuple[T, R]]:
374
+ """
375
+ Perform a relational join with another iterable.
376
+
377
+ Args:
378
+ other: Iterable to join with.
379
+ left_on: Function to extract the join key from the left iterable.
380
+ right_on: Function to extract the join key from the right iterable.
381
+ left_default: Default value for missing elements in the left iterable. Defaults to None.
382
+ right_default: Default value for missing elements in the right iterable. Defaults to None.
383
+ Example:
384
+ ```python
385
+ >>> import pyochain as pc
386
+ >>> colors = pc.Iter.from_(["blue", "red"])
387
+ >>> sizes = ["S", "M"]
388
+ >>> colors.join(sizes, left_on=lambda c: c, right_on=lambda s: s).into(list)
389
+ [(None, 'S'), (None, 'M'), ('blue', None), ('red', None)]
390
+
391
+ ```
392
+ """
393
+
394
+ def _join(data: Iterable[T]) -> Iterator[tuple[T, R]]:
395
+ return cz.itertoolz.join(
396
+ leftkey=left_on,
397
+ leftseq=data,
398
+ rightkey=right_on,
399
+ rightseq=other,
400
+ left_default=left_default,
401
+ right_default=right_default,
402
+ )
403
+
404
+ return self._lazy(_join)