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,510 @@
1
+ from __future__ import annotations
2
+
3
+ import itertools
4
+ from collections.abc import Callable, Generator, Iterable, Iterator
5
+ from functools import partial
6
+ from typing import TYPE_CHECKING, Any, TypeGuard
7
+
8
+ import cytoolz as cz
9
+ import more_itertools as mit
10
+
11
+ from .._core import IterWrapper
12
+
13
+ if TYPE_CHECKING:
14
+ from ._main import Iter
15
+
16
+
17
+ class BaseFilter[T](IterWrapper[T]):
18
+ def filter(self, func: Callable[[T], bool]) -> Iter[T]:
19
+ """
20
+ Return an iterator yielding those items of iterable for which function is true.
21
+
22
+ Args:
23
+ func: Function to evaluate each item.
24
+ Example:
25
+ ```python
26
+ >>> import pyochain as pc
27
+ >>> pc.Iter.from_([1, 2, 3]).filter(lambda x: x > 1).into(list)
28
+ [2, 3]
29
+
30
+ ```
31
+ """
32
+
33
+ def _filter(data: Iterable[T]) -> Iterator[T]:
34
+ return (x for x in data if func(x))
35
+
36
+ return self._lazy(_filter)
37
+
38
+ def filter_isin(self, values: Iterable[T]) -> Iter[T]:
39
+ """
40
+ Return elements that are in the given values iterable.
41
+
42
+ Args:
43
+ values: Iterable of values to check membership against.
44
+ Example:
45
+ ```python
46
+ >>> import pyochain as pc
47
+ >>> pc.Iter.from_([1, 2, 3, 4]).filter_isin([2, 4, 6]).into(list)
48
+ [2, 4]
49
+
50
+ ```
51
+ """
52
+
53
+ def _filter_isin(data: Iterable[T]) -> Generator[T, None, None]:
54
+ value_set: set[T] = set(values)
55
+ return (x for x in data if x in value_set)
56
+
57
+ return self._lazy(_filter_isin)
58
+
59
+ def filter_notin(self, values: Iterable[T]) -> Iter[T]:
60
+ """
61
+ Return elements that are not in the given values iterable.
62
+
63
+ Args:
64
+ values: Iterable of values to exclude.
65
+ Example:
66
+ ```python
67
+ >>> import pyochain as pc
68
+ >>> pc.Iter.from_([1, 2, 3, 4]).filter_notin([2, 4, 6]).into(list)
69
+ [1, 3]
70
+
71
+ ```
72
+ """
73
+
74
+ def _filter_notin(data: Iterable[T]) -> Generator[T, None, None]:
75
+ value_set: set[T] = set(values)
76
+ return (x for x in data if x not in value_set)
77
+
78
+ return self._lazy(_filter_notin)
79
+
80
+ def filter_contain(
81
+ self: IterWrapper[str], text: str, format: Callable[[str], str] | None = None
82
+ ) -> Iter[str]:
83
+ """
84
+ Return elements that contain the given text.
85
+
86
+ Optionally, a format function can be provided to preprocess each element before checking for the substring.
87
+
88
+ Args:
89
+ text: Substring to check for.
90
+ format: Optional function to preprocess each element before checking. Defaults to None.
91
+ Example:
92
+ ```python
93
+ >>> import pyochain as pc
94
+ >>>
95
+ >>> data = pc.Seq(["apple", "banana", "cherry", "date"])
96
+ >>> data.iter().filter_contain("ana").into(list)
97
+ ['banana']
98
+ >>> data.iter().map(str.upper).filter_contain("ana", str.lower).into(list)
99
+ ['BANANA']
100
+
101
+ ```
102
+ """
103
+
104
+ def _filter_contain(data: Iterable[str]) -> Generator[str, None, None]:
105
+ def _(x: str) -> bool:
106
+ formatted = format(x) if format else x
107
+ return text in formatted
108
+
109
+ return (x for x in data if _(x))
110
+
111
+ return self._lazy(_filter_contain)
112
+
113
+ def filter_attr[U](self, attr: str, dtype: type[U] = object) -> Iter[U]:
114
+ """
115
+ Return elements that have the given attribute.
116
+
117
+ The provided dtype is not checked at runtime for performance considerations.
118
+
119
+ Args:
120
+ attr: Name of the attribute to check for.
121
+ dtype: Expected type of the attribute. Defaults to object.
122
+ Example:
123
+ ```python
124
+ >>> import pyochain as pc
125
+ >>> pc.Iter.from_(["hello", "world", 2, 5]).filter_attr("capitalize", str).into(
126
+ ... list
127
+ ... )
128
+ ['hello', 'world']
129
+
130
+ ```
131
+ """
132
+
133
+ def check(data: Iterable[Any]) -> Generator[U, None, None]:
134
+ def _(x: Any) -> TypeGuard[U]:
135
+ return hasattr(x, attr)
136
+
137
+ return (x for x in data if _(x))
138
+
139
+ return self._lazy(check)
140
+
141
+ def filter_false(self, func: Callable[[T], bool]) -> Iter[T]:
142
+ """
143
+ Return elements for which func is false.
144
+
145
+ Args:
146
+ func: Function to evaluate each item.
147
+ Example:
148
+ ```python
149
+ >>> import pyochain as pc
150
+ >>> pc.Iter.from_([1, 2, 3]).filter_false(lambda x: x > 1).into(list)
151
+ [1]
152
+
153
+ ```
154
+ """
155
+ return self._lazy(partial(itertools.filterfalse, func))
156
+
157
+ def filter_except(
158
+ self, func: Callable[[T], object], *exceptions: type[BaseException]
159
+ ) -> Iter[T]:
160
+ """
161
+ Yield the items from iterable for which the validator function does not raise one of the specified exceptions.
162
+
163
+ Validator is called for each item in iterable.
164
+
165
+ It should be a function that accepts one argument and raises an exception if that item is not valid.
166
+
167
+ If an exception other than one given by exceptions is raised by validator, it is raised like normal.
168
+
169
+ Args:
170
+ func: Validator function to apply to each item.
171
+ exceptions: Exceptions to catch and ignore.
172
+ Example:
173
+ ```python
174
+ >>> import pyochain as pc
175
+ >>> iterable = ["1", "2", "three", "4", None]
176
+ >>> pc.Iter.from_(iterable).filter_except(int, ValueError, TypeError).into(list)
177
+ ['1', '2', '4']
178
+
179
+ ```
180
+ """
181
+
182
+ def _filter_except(data: Iterable[T]) -> Iterator[T]:
183
+ return mit.filter_except(func, data, *exceptions)
184
+
185
+ return self._lazy(_filter_except)
186
+
187
+ def take_while(self, predicate: Callable[[T], bool]) -> Iter[T]:
188
+ """
189
+ Take items while predicate holds.
190
+
191
+ Args:
192
+ predicate: Function to evaluate each item.
193
+ Example:
194
+ ```python
195
+ >>> import pyochain as pc
196
+ >>> pc.Iter.from_([1, 2, 0]).take_while(lambda x: x > 0).into(list)
197
+ [1, 2]
198
+
199
+ ```
200
+ """
201
+ return self._lazy(partial(itertools.takewhile, predicate))
202
+
203
+ def skip_while(self, predicate: Callable[[T], bool]) -> Iter[T]:
204
+ """
205
+ Drop items while predicate holds.
206
+
207
+ Args:
208
+ predicate: Function to evaluate each item.
209
+ Example:
210
+ ```python
211
+ >>> import pyochain as pc
212
+ >>> pc.Iter.from_([1, 2, 0]).skip_while(lambda x: x > 0).into(list)
213
+ [0]
214
+
215
+ ```
216
+ """
217
+ return self._lazy(partial(itertools.dropwhile, predicate))
218
+
219
+ def compress(self, *selectors: bool) -> Iter[T]:
220
+ """
221
+ Filter elements using a boolean selector iterable.
222
+
223
+ Args:
224
+ selectors: Boolean values indicating which elements to keep.
225
+ Example:
226
+ ```python
227
+ >>> import pyochain as pc
228
+ >>> pc.Iter.from_("ABCDEF").compress(1, 0, 1, 0, 1, 1).into(list)
229
+ ['A', 'C', 'E', 'F']
230
+
231
+ ```
232
+ """
233
+ return self._lazy(itertools.compress, selectors)
234
+
235
+ def unique(self, key: Callable[[T], Any] | None = None) -> Iter[T]:
236
+ """
237
+ Return only unique elements of the iterable.
238
+
239
+ Args:
240
+ key: Function to transform items before comparison. Defaults to None.
241
+ Example:
242
+ ```python
243
+ >>> import pyochain as pc
244
+ >>> pc.Iter.from_([1, 2, 3]).unique().into(list)
245
+ [1, 2, 3]
246
+ >>> pc.Iter.from_([1, 2, 1, 3]).unique().into(list)
247
+ [1, 2, 3]
248
+
249
+ ```
250
+ Uniqueness can be defined by key keyword
251
+ ```python
252
+ >>> pc.Iter.from_(["cat", "mouse", "dog", "hen"]).unique(key=len).into(list)
253
+ ['cat', 'mouse']
254
+
255
+ ```
256
+ """
257
+ return self._lazy(cz.itertoolz.unique, key=key)
258
+
259
+ def take(self, n: int) -> Iter[T]:
260
+ """
261
+ Creates an iterator that yields the first n elements, or fewer if the underlying iterator ends sooner.
262
+
263
+ `Iter.take(n)` yields elements until n elements are yielded or the end of the iterator is reached (whichever happens first).
264
+
265
+ The returned iterator is either:
266
+
267
+ - A prefix of length n if the original iterator contains at least n elements
268
+ - All of the (fewer than n) elements of the original iterator if it contains fewer than n elements.
269
+
270
+ Args:
271
+ n: Number of elements to take.
272
+ Example:
273
+ ```python
274
+ >>> import pyochain as pc
275
+ >>> data = [1, 2, 3]
276
+ >>> pc.Iter.from_(data).take(2).into(list)
277
+ [1, 2]
278
+ >>> pc.Iter.from_(data).take(5).into(list)
279
+ [1, 2, 3]
280
+
281
+ ```
282
+ """
283
+
284
+ return self._lazy(partial(cz.itertoolz.take, n))
285
+
286
+ def skip(self, n: int) -> Iter[T]:
287
+ """
288
+ Drop first n elements.
289
+
290
+ Args:
291
+ n: Number of elements to skip.
292
+ Example:
293
+ ```python
294
+ >>> import pyochain as pc
295
+ >>> pc.Iter.from_([1, 2, 3]).skip(1).into(list)
296
+ [2, 3]
297
+
298
+ ```
299
+ """
300
+ return self._lazy(partial(cz.itertoolz.drop, n))
301
+
302
+ def unique_justseen(self, key: Callable[[T], Any] | None = None) -> Iter[T]:
303
+ """
304
+ Yields elements in order, ignoring serial duplicates.
305
+
306
+ Args:
307
+ key: Function to transform items before comparison. Defaults to None.
308
+ Example:
309
+ ```python
310
+ >>> import pyochain as pc
311
+ >>> pc.Iter.from_("AAAABBBCCDAABBB").unique_justseen().into(list)
312
+ ['A', 'B', 'C', 'D', 'A', 'B']
313
+ >>> pc.Iter.from_("ABBCcAD").unique_justseen(str.lower).into(list)
314
+ ['A', 'B', 'C', 'A', 'D']
315
+
316
+ ```
317
+ """
318
+ return self._lazy(mit.unique_justseen, key=key)
319
+
320
+ def unique_in_window(
321
+ self, n: int, key: Callable[[T], Any] | None = None
322
+ ) -> Iter[T]:
323
+ """
324
+ Yield the items from iterable that haven't been seen recently.
325
+
326
+ The items in iterable must be hashable.
327
+
328
+ Args:
329
+ n: Size of the lookback window.
330
+ key: Function to transform items before comparison. Defaults to None.
331
+ Example:
332
+ ```python
333
+ >>> import pyochain as pc
334
+ >>> iterable = [0, 1, 0, 2, 3, 0]
335
+ >>> n = 3
336
+ >>> pc.Iter.from_(iterable).unique_in_window(n).into(list)
337
+ [0, 1, 2, 3, 0]
338
+
339
+ ```
340
+ The key function, if provided, will be used to determine uniqueness:
341
+ ```python
342
+ >>> pc.Iter.from_("abAcda").unique_in_window(3, key=str.lower).into(list)
343
+ ['a', 'b', 'c', 'd', 'a']
344
+
345
+ ```
346
+ """
347
+ return self._lazy(mit.unique_in_window, n, key=key)
348
+
349
+ def extract(self, indices: Iterable[int]) -> Iter[T]:
350
+ """
351
+ Yield values at the specified indices.
352
+
353
+ - The iterable is consumed lazily and can be infinite.
354
+ - The indices are consumed immediately and must be finite.
355
+ - Raises IndexError if an index lies beyond the iterable.
356
+ - Raises ValueError for negative indices.
357
+
358
+ Args:
359
+ indices: Iterable of indices to extract values from.
360
+ Example:
361
+ ```python
362
+ >>> import pyochain as pc
363
+ >>> text = "abcdefghijklmnopqrstuvwxyz"
364
+ >>> pc.Iter.from_(text).extract([7, 4, 11, 11, 14]).into(list)
365
+ ['h', 'e', 'l', 'l', 'o']
366
+
367
+ ```
368
+ """
369
+ return self._lazy(mit.extract, indices)
370
+
371
+ def every(self, index: int) -> Iter[T]:
372
+ """
373
+ Return every nth item starting from first.
374
+
375
+ Args:
376
+ index: Step size for selecting items.
377
+ Example:
378
+ ```python
379
+ >>> import pyochain as pc
380
+ >>> pc.Iter.from_([10, 20, 30, 40]).every(2).into(list)
381
+ [10, 30]
382
+
383
+ ```
384
+ """
385
+ return self._lazy(partial(cz.itertoolz.take_nth, index))
386
+
387
+ def slice(
388
+ self, start: int | None = None, stop: int | None = None, step: int | None = None
389
+ ) -> Iter[T]:
390
+ """
391
+ Return a slice of the iterable.
392
+
393
+ Args:
394
+ start: Starting index of the slice. Defaults to None.
395
+ stop: Ending index of the slice. Defaults to None.
396
+ step: Step size for the slice. Defaults to None.
397
+ Example:
398
+ ```python
399
+ >>> import pyochain as pc
400
+ >>> pc.Iter.from_([1, 2, 3, 4, 5]).slice(1, 4).into(list)
401
+ [2, 3, 4]
402
+ >>> pc.Iter.from_([1, 2, 3, 4, 5]).slice(step=2).into(list)
403
+ [1, 3, 5]
404
+
405
+ ```
406
+ """
407
+
408
+ def _slice(data: Iterable[T]) -> Iterator[T]:
409
+ return itertools.islice(data, start, stop, step)
410
+
411
+ return self._lazy(_slice)
412
+
413
+ def filter_subclass[U: type[Any], R](
414
+ self: IterWrapper[U], parent: type[R], keep_parent: bool = True
415
+ ) -> Iter[type[R]]:
416
+ """
417
+ Return elements that are subclasses of the given class, optionally excluding the parent class itself.
418
+
419
+ Args:
420
+ parent: Parent class to check against.
421
+ keep_parent: Whether to include the parent class itself. Defaults to True.
422
+ Example:
423
+ ```python
424
+ >>> import pyochain as pc
425
+ >>> class A:
426
+ ... pass
427
+ >>> class B(A):
428
+ ... pass
429
+ >>> class C:
430
+ ... pass
431
+ >>> def name(cls: type[Any]) -> str:
432
+ ... return cls.__name__
433
+ >>>
434
+ >>> data = pc.Seq([A, B, C])
435
+ >>> data.iter().filter_subclass(A).map(name).into(list)
436
+ ['A', 'B']
437
+ >>> data.iter().filter_subclass(A, keep_parent=False).map(name).into(list)
438
+ ['B']
439
+
440
+ ```
441
+ """
442
+
443
+ def _filter_subclass(
444
+ data: Iterable[type[Any]],
445
+ ) -> Generator[type[R], None, None]:
446
+ if keep_parent:
447
+ return (x for x in data if issubclass(x, parent))
448
+ else:
449
+ return (x for x in data if issubclass(x, parent) and x is not parent)
450
+
451
+ return self._lazy(_filter_subclass)
452
+
453
+ def filter_type[R](self, typ: type[R]) -> Iter[R]:
454
+ """
455
+ Return elements that are instances of the given type.
456
+
457
+ Args:
458
+ typ: Type to check against.
459
+ Example:
460
+ ```python
461
+ >>> import pyochain as pc
462
+ >>> pc.Iter.from_([1, "two", 3.0, "four", 5]).filter_type(int).into(list)
463
+ [1, 5]
464
+
465
+ ```
466
+ """
467
+
468
+ def _filter_type(data: Iterable[T]) -> Generator[R, None, None]:
469
+ return (x for x in data if isinstance(x, typ))
470
+
471
+ return self._lazy(_filter_type)
472
+
473
+ def filter_callable(self) -> Iter[Callable[..., Any]]:
474
+ """
475
+ Return only elements that are callable.
476
+
477
+ Example:
478
+ ```python
479
+ >>> import pyochain as pc
480
+ >>> pc.Iter.from_([len, 42, str, None, list]).filter_callable().into(list)
481
+ [<built-in function len>, <class 'str'>, <class 'list'>]
482
+
483
+ ```
484
+ """
485
+
486
+ def _filter_callable(
487
+ data: Iterable[T],
488
+ ) -> Generator[Callable[..., Any], None, None]:
489
+ return (x for x in data if callable(x))
490
+
491
+ return self._lazy(_filter_callable)
492
+
493
+ def filter_map[R](self, func: Callable[[T], R]) -> Iter[R]:
494
+ """
495
+ Apply func to every element of iterable, yielding only those which are not None.
496
+
497
+ Args:
498
+ func: Function to apply to each item.
499
+ Example:
500
+ ```python
501
+ >>> import pyochain as pc
502
+ >>> def to_int(s: str) -> int | None:
503
+ ... return int(s) if s.isnumeric() else None
504
+ >>> elems = ["1", "a", "2", "b", "3"]
505
+ >>> pc.Iter.from_(elems).filter_map(to_int).into(list)
506
+ [1, 2, 3]
507
+
508
+ ```
509
+ """
510
+ return self._lazy(partial(mit.filter_map, func))