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,366 @@
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 random import Random
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ import cytoolz as cz
10
+ import more_itertools as mit
11
+
12
+ from .._core import IterWrapper, peek, peekn
13
+
14
+ if TYPE_CHECKING:
15
+ from ._main import Iter
16
+
17
+
18
+ def _too_short(item_count: int):
19
+ return mit.raise_(
20
+ ValueError,
21
+ f"Too few items in iterable (got {item_count})",
22
+ )
23
+
24
+
25
+ def _too_long(item_count: int):
26
+ return mit.raise_(
27
+ ValueError,
28
+ f"Too many items in iterable (got at least {item_count})",
29
+ )
30
+
31
+
32
+ class BaseProcess[T](IterWrapper[T]):
33
+ def cycle(self) -> Iter[T]:
34
+ """
35
+ Repeat the sequence indefinitely.
36
+
37
+ **Warning** ⚠️
38
+ This creates an infinite iterator.
39
+ Be sure to use Iter.take() or Iter.slice() to limit the number of items taken.
40
+ ```python
41
+
42
+ Example:
43
+ >>> import pyochain as pc
44
+ >>> pc.Iter.from_([1, 2]).cycle().take(5).into(list)
45
+ [1, 2, 1, 2, 1]
46
+
47
+ ```
48
+ """
49
+ return self._lazy(itertools.cycle)
50
+
51
+ def interpose(self, element: T) -> Iter[T]:
52
+ """
53
+ Interpose element between items and return a new Iterable wrapper.
54
+
55
+ Args:
56
+ element: The element to interpose between items.
57
+ Example:
58
+ ```python
59
+ >>> import pyochain as pc
60
+ >>> pc.Iter.from_([1, 2]).interpose(0).into(list)
61
+ [1, 0, 2]
62
+
63
+ ```
64
+ """
65
+ return self._lazy(partial(cz.itertoolz.interpose, element))
66
+
67
+ def random_sample(
68
+ self, probability: float, state: Random | int | None = None
69
+ ) -> Iter[T]:
70
+ """
71
+ Return elements from a sequence with probability of prob.
72
+
73
+ Returns a lazy iterator of random items from seq.
74
+
75
+ random_sample considers each item independently and without replacement.
76
+
77
+ See below how the first time it returned 13 items and the next time it returned 6 items.
78
+
79
+ Args:
80
+ probability: The probability of including each element.
81
+ state: Random state or seed for deterministic sampling.
82
+ ```python
83
+ >>> import pyochain as pc
84
+ >>> data = pc.Seq(list(range(100)))
85
+ >>> data.iter().random_sample(0.1).into(list) # doctest: +SKIP
86
+ [6, 9, 19, 35, 45, 50, 58, 62, 68, 72, 78, 86, 95]
87
+ >>> data.iter().random_sample(0.1).into(list) # doctest: +SKIP
88
+ [6, 44, 54, 61, 69, 94]
89
+ ```
90
+ Providing an integer seed for random_state will result in deterministic sampling.
91
+
92
+ Given the same seed it will return the same sample every time.
93
+ ```python
94
+ >>> data.iter().random_sample(0.1, state=2016).into(list)
95
+ [7, 9, 19, 25, 30, 32, 34, 48, 59, 60, 81, 98]
96
+ >>> data.iter().random_sample(0.1, state=2016).into(list)
97
+ [7, 9, 19, 25, 30, 32, 34, 48, 59, 60, 81, 98]
98
+
99
+ ```
100
+ random_state can also be any object with a method random that returns floats between 0.0 and 1.0 (exclusive).
101
+ ```python
102
+ >>> from random import Random
103
+ >>> randobj = Random(2016)
104
+ >>> data.iter().random_sample(0.1, state=randobj).into(list)
105
+ [7, 9, 19, 25, 30, 32, 34, 48, 59, 60, 81, 98]
106
+
107
+ ```
108
+ """
109
+
110
+ return self._lazy(
111
+ partial(cz.itertoolz.random_sample, probability, random_state=state)
112
+ )
113
+
114
+ def accumulate(self, func: Callable[[T, T], T]) -> Iter[T]:
115
+ """
116
+ Return cumulative application of binary op provided by the function.
117
+
118
+ Args:
119
+ func: A binary function to apply cumulatively.
120
+ ```python
121
+ >>> import pyochain as pc
122
+ >>> pc.Iter.from_([1, 2, 3]).accumulate(lambda a, b: a + b).into(list)
123
+ [1, 3, 6]
124
+
125
+ ```
126
+ """
127
+ return self._lazy(partial(cz.itertoolz.accumulate, func))
128
+
129
+ def insert_left(self, value: T) -> Iter[T]:
130
+ """
131
+ Prepend value to the sequence and return a new Iterable wrapper.
132
+
133
+ Args:
134
+ value: The value to prepend.
135
+ ```python
136
+ >>> import pyochain as pc
137
+ >>> pc.Iter.from_([2, 3]).insert_left(1).into(list)
138
+ [1, 2, 3]
139
+
140
+ ```
141
+ """
142
+ return self._lazy(partial(cz.itertoolz.cons, value))
143
+
144
+ def peekn(self, n: int) -> Iter[T]:
145
+ """
146
+ Print and return sequence after peeking n items.
147
+
148
+ Args:
149
+ n: Number of items to peek.
150
+ ```python
151
+ >>> import pyochain as pc
152
+ >>> pc.Iter.from_([1, 2, 3]).peekn(2).into(list)
153
+ Peeked 2 values: (1, 2)
154
+ [1, 2, 3]
155
+
156
+ ```
157
+ """
158
+ return self._lazy(peekn, n)
159
+
160
+ def peek(self) -> Iter[T]:
161
+ """
162
+ Print and return sequence after peeking first item.
163
+ ```python
164
+ >>> import pyochain as pc
165
+ >>> pc.Iter.from_([1, 2]).peek().into(list)
166
+ Peeked value: 1
167
+ [1, 2]
168
+
169
+ ```
170
+ """
171
+ return self._lazy(peek)
172
+
173
+ def merge_sorted(
174
+ self, *others: Iterable[T], sort_on: Callable[[T], Any] | None = None
175
+ ) -> Iter[T]:
176
+ """
177
+ Merge already-sorted sequences.
178
+
179
+ Args:
180
+ others: Other sorted iterables to merge.
181
+ sort_on: Optional key function for sorting.
182
+ ```python
183
+ >>> import pyochain as pc
184
+ >>> pc.Iter.from_([1, 3]).merge_sorted([2, 4]).into(list)
185
+ [1, 2, 3, 4]
186
+
187
+ ```
188
+ """
189
+ return self._lazy(cz.itertoolz.merge_sorted, *others, key=sort_on)
190
+
191
+ def interleave(self, *others: Iterable[T]) -> Iter[T]:
192
+ """
193
+ Interleave multiple sequences element-wise.
194
+
195
+ Args:
196
+ others: Other iterables to interleave.
197
+ ```python
198
+ >>> import pyochain as pc
199
+ >>> pc.Iter.from_([1, 2]).interleave([3, 4]).into(list)
200
+ [1, 3, 2, 4]
201
+
202
+ ```
203
+ """
204
+
205
+ def _interleave(data: Iterable[T]) -> Iterator[T]:
206
+ return cz.itertoolz.interleave((data, *others))
207
+
208
+ return self._lazy(_interleave)
209
+
210
+ def chain(self, *others: Iterable[T]) -> Iter[T]:
211
+ """
212
+ Concatenate zero or more iterables, any of which may be infinite.
213
+
214
+ An infinite sequence will prevent the rest of the arguments from being included.
215
+
216
+ We use chain.from_iterable rather than chain(*seqs) so that seqs can be a generator.
217
+
218
+ Args:
219
+ others: Other iterables to concatenate.
220
+ ```python
221
+ >>> import pyochain as pc
222
+ >>> pc.Iter.from_([1, 2]).chain([3, 4], [5]).into(list)
223
+ [1, 2, 3, 4, 5]
224
+
225
+ ```
226
+ """
227
+
228
+ def _chain(data: Iterable[T]) -> Iterator[T]:
229
+ return cz.itertoolz.concat((data, *others))
230
+
231
+ return self._lazy(_chain)
232
+
233
+ def elements(self) -> Iter[T]:
234
+ """
235
+ Iterator over elements repeating each as many times as its count.
236
+
237
+ Note:
238
+ if an element's count has been set to zero or is a negative
239
+ number, elements() will ignore it.
240
+ ```python
241
+ >>> import pyochain as pc
242
+ >>> pc.Iter.from_("ABCABC").elements().sort().unwrap()
243
+ ['A', 'A', 'B', 'B', 'C', 'C']
244
+
245
+ ```
246
+ Knuth's example for prime factors of 1836: 2**2 * 3**3 * 17**1
247
+ ```python
248
+ >>> import math
249
+ >>> data = [2, 2, 3, 3, 3, 17]
250
+ >>> pc.Iter.from_(data).elements().into(math.prod)
251
+ 1836
252
+
253
+ ```
254
+ """
255
+ from collections import Counter
256
+
257
+ def _elements(data: Iterable[T]) -> Iterator[T]:
258
+ return Counter(data).elements()
259
+
260
+ return self._lazy(_elements)
261
+
262
+ def reverse(self) -> Iter[T]:
263
+ """
264
+ Return a new Iterable wrapper with elements in reverse order.
265
+
266
+ The result is a new iterable over the reversed sequence.
267
+
268
+ Note:
269
+ This method must consume the entire iterable to perform the reversal.
270
+ ```python
271
+ >>> import pyochain as pc
272
+ >>> pc.Iter.from_([1, 2, 3]).reverse().into(list)
273
+ [3, 2, 1]
274
+
275
+ ```
276
+ """
277
+
278
+ def _reverse(data: Iterable[T]) -> Iterator[T]:
279
+ return reversed(list(data))
280
+
281
+ return self._lazy(_reverse)
282
+
283
+ def is_strictly_n(
284
+ self,
285
+ n: int,
286
+ too_short: Callable[[int], Iterator[T]] | Callable[[int], None] = _too_short,
287
+ too_long: Callable[[int], Iterator[T]] | Callable[[int], None] = _too_long,
288
+ ) -> Iter[T]:
289
+ """
290
+ Validate that *iterable* has exactly *n* items and return them if it does.
291
+
292
+ If it has fewer than *n* items, call function *too_short* with the actual number of items.
293
+
294
+ If it has more than *n* items, call function *too_long* with the number `n + 1`.
295
+
296
+ Args:
297
+ n: The exact number of items expected.
298
+ too_short: Function to call if there are too few items.
299
+ too_long: Function to call if there are too many items.
300
+ ```python
301
+ >>> import pyochain as pc
302
+ >>> iterable = ["a", "b", "c", "d"]
303
+ >>> n = 4
304
+ >>> pc.Iter.from_(iterable).is_strictly_n(n).into(list)
305
+ ['a', 'b', 'c', 'd']
306
+
307
+ ```
308
+ Note that the returned iterable must be consumed in order for the check to
309
+ be made.
310
+
311
+ By default, *too_short* and *too_long* are functions that raise`ValueError`.
312
+ ```python
313
+ >>> pc.Iter.from_("ab").is_strictly_n(3).into(
314
+ ... list
315
+ ... ) # doctest: +IGNORE_EXCEPTION_DETAIL
316
+ Traceback (most recent call last):
317
+ ...
318
+ ValueError: too few items in iterable (got 2)
319
+
320
+ >>> pc.Iter.from_("abc").is_strictly_n(2).into(
321
+ ... list
322
+ ... ) # doctest: +IGNORE_EXCEPTION_DETAIL
323
+ Traceback (most recent call last):
324
+ ...
325
+ ValueError: too many items in iterable (got at least 3)
326
+
327
+ ```
328
+ You can instead supply functions that do something else.
329
+
330
+ *too_short* will be called with the number of items in *iterable*.
331
+
332
+ *too_long* will be called with `n + 1`.
333
+ ```python
334
+ >>> def too_short(item_count):
335
+ ... raise RuntimeError
336
+ >>> pc.Iter.from_("abcd").is_strictly_n(6, too_short=too_short).into(list)
337
+ Traceback (most recent call last):
338
+ ...
339
+ RuntimeError
340
+ >>> def too_long(item_count):
341
+ ... print("The boss is going to hear about this")
342
+ >>> pc.Iter.from_("abcdef").is_strictly_n(4, too_long=too_long).into(list)
343
+ The boss is going to hear about this
344
+ ['a', 'b', 'c', 'd']
345
+
346
+ ```
347
+ """
348
+
349
+ def strictly_n_(iterable: Iterable[T]) -> Generator[T, Any, None]:
350
+ """from more_itertools.strictly_n"""
351
+ it = iter(iterable)
352
+
353
+ sent = 0
354
+ for item in itertools.islice(it, n):
355
+ yield item
356
+ sent += 1
357
+
358
+ if sent < n:
359
+ too_short(sent)
360
+ return
361
+
362
+ for item in it:
363
+ too_long(n + 1)
364
+ return
365
+
366
+ return self._lazy(strictly_n_)
@@ -0,0 +1,241 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable, Iterable
4
+ from typing import TYPE_CHECKING
5
+
6
+ import rolling
7
+
8
+ from .._core import IterWrapper
9
+
10
+ if TYPE_CHECKING:
11
+ from ._main import Iter
12
+
13
+
14
+ class BaseRolling[T](IterWrapper[T]):
15
+ def rolling_mean(self, window_size: int) -> Iter[float]:
16
+ """
17
+ Compute the rolling mean.
18
+
19
+ Args:
20
+ window_size: Size of the rolling window.
21
+ ```python
22
+ >>> import pyochain as pc
23
+ >>> pc.Iter.from_([1, 2, 3, 4, 5]).rolling_mean(3).into(list)
24
+ [2.0, 3.0, 4.0]
25
+
26
+ ```
27
+ """
28
+ return self._lazy(rolling.Mean, window_size)
29
+
30
+ def rolling_median(self, window_size: int) -> Iter[T]:
31
+ """
32
+ Compute the rolling median.
33
+
34
+ Args:
35
+ window_size: Size of the rolling window.
36
+ ```python
37
+ >>> import pyochain as pc
38
+ >>> pc.Iter.from_([1, 3, 2, 5, 4]).rolling_median(3).into(list)
39
+ [2, 3, 4]
40
+
41
+ ```
42
+ """
43
+ return self._lazy(rolling.Median, window_size)
44
+
45
+ def rolling_sum(self, window_size: int) -> Iter[T]:
46
+ """
47
+ Compute the rolling sum.
48
+
49
+ Will return integers if the input is integers, floats if the input is floats or mixed.
50
+
51
+ Args:
52
+ window_size: Size of the rolling window.
53
+ ```python
54
+ >>> import pyochain as pc
55
+ >>> pc.Iter.from_([1.0, 2, 3, 4, 5]).rolling_sum(3).into(list)
56
+ [6.0, 9.0, 12.0]
57
+
58
+ ```
59
+ """
60
+ return self._lazy(rolling.Sum, window_size)
61
+
62
+ def rolling_min(self, window_size: int) -> Iter[T]:
63
+ """
64
+ Compute the rolling minimum.
65
+
66
+ Args:
67
+ window_size: Size of the rolling window.
68
+ ```python
69
+ >>> import pyochain as pc
70
+ >>> pc.Iter.from_([3, 1, 4, 1, 5, 9, 2]).rolling_min(3).into(list)
71
+ [1, 1, 1, 1, 2]
72
+
73
+ ```
74
+ """
75
+ return self._lazy(rolling.Min, window_size)
76
+
77
+ def rolling_max(self, window_size: int) -> Iter[T]:
78
+ """
79
+ Compute the rolling maximum.
80
+
81
+ Args:
82
+ window_size: Size of the rolling window.
83
+ ```python
84
+ >>> import pyochain as pc
85
+ >>> pc.Iter.from_([3, 1, 4, 1, 5, 9, 2]).rolling_max(3).into(list)
86
+ [4, 4, 5, 9, 9]
87
+
88
+ ```
89
+ """
90
+ return self._lazy(rolling.Max, window_size)
91
+
92
+ def rolling_var(self, window_size: int) -> Iter[float]:
93
+ """
94
+ Compute the rolling variance.
95
+
96
+ Args:
97
+ window_size: Size of the rolling window.
98
+ ```python
99
+ >>> import pyochain as pc
100
+ >>> pc.Iter.from_([1, 2, 4, 1, 4]).rolling_var(3).round(2).into(list)
101
+ [2.33, 2.33, 3.0]
102
+
103
+ ```
104
+ """
105
+ return self._lazy(rolling.Var, window_size)
106
+
107
+ def rolling_std(self, window_size: int) -> Iter[float]:
108
+ """
109
+ Compute the rolling standard deviation.
110
+
111
+ Args:
112
+ window_size: Size of the rolling window.
113
+ ```python
114
+ >>> import pyochain as pc
115
+ >>> pc.Iter.from_([1, 2, 4, 1, 4]).rolling_std(3).round(2).into(list)
116
+ [1.53, 1.53, 1.73]
117
+
118
+ ```
119
+ """
120
+ return self._lazy(rolling.Std, window_size)
121
+
122
+ def rolling_kurtosis(self, window_size: int) -> Iter[float]:
123
+ """
124
+ Compute the rolling kurtosis.
125
+
126
+ Args:
127
+ window_size: Size of the rolling window. Must be at least 4.
128
+ ```python
129
+ >>> import pyochain as pc
130
+ >>> pc.Iter.from_([1, 2, 4, 1, 4]).rolling_kurtosis(4).into(list)
131
+ [1.5, -3.901234567901234]
132
+
133
+ ```
134
+ """
135
+ return self._lazy(rolling.Kurtosis, window_size)
136
+
137
+ def rolling_skew(self, window_size: int) -> Iter[float]:
138
+ """
139
+ Compute the rolling skewness.
140
+
141
+ Args:
142
+ window_size: Size of the rolling window. Must be at least 3.
143
+ ```python
144
+ >>> import pyochain as pc
145
+ >>> pc.Iter.from_([1, 2, 4, 1, 4]).rolling_skew(3).round(2).into(list)
146
+ [0.94, 0.94, -1.73]
147
+
148
+ ```
149
+ """
150
+ return self._lazy(rolling.Skew, window_size)
151
+
152
+ def rolling_all(self, window_size: int) -> Iter[bool]:
153
+ """
154
+ Compute whether all values in the window evaluate to True.
155
+
156
+ Args:
157
+ window_size: Size of the rolling window.
158
+ ```python
159
+ >>> import pyochain as pc
160
+ >>> pc.Iter.from_([True, True, False, True, True]).rolling_all(2).into(list)
161
+ [True, False, False, True]
162
+
163
+ ```
164
+ """
165
+ return self._lazy(rolling.All, window_size)
166
+
167
+ def rolling_any(self, window_size: int) -> Iter[bool]:
168
+ """
169
+ Compute whether any value in the window evaluates to True.
170
+
171
+ Args:
172
+ window_size: Size of the rolling window.
173
+ ```python
174
+ >>> import pyochain as pc
175
+ >>> pc.Iter.from_([True, True, False, True, True]).rolling_any(2).into(list)
176
+ [True, True, True, True]
177
+
178
+ ```
179
+ """
180
+ return self._lazy(rolling.Any, window_size)
181
+
182
+ def rolling_product(self, window_size: int) -> Iter[float]:
183
+ """
184
+ Compute the rolling product.
185
+
186
+ Args:
187
+ window_size: Size of the rolling window.
188
+ ```python
189
+ >>> import pyochain as pc
190
+ >>> pc.Iter.from_([1, 2, 3, 4, 5]).rolling_product(3).into(list)
191
+ [6.0, 24.0, 60.0]
192
+
193
+ ```
194
+ """
195
+ return self._lazy(rolling.Product, window_size)
196
+
197
+ def rolling_apply[R](
198
+ self, func: Callable[[Iterable[T]], R], window_size: int
199
+ ) -> Iter[R]:
200
+ """
201
+ Apply a custom function to each rolling window.
202
+
203
+ The function should accept an iterable and return a single value.
204
+
205
+ Args:
206
+ func: Function to apply to each rolling window.
207
+ window_size: Size of the rolling window.
208
+ ```python
209
+ >>> import pyochain as pc
210
+ >>> def range_func(window):
211
+ ... return max(window) - min(window)
212
+ >>> pc.Iter.from_([1, 3, 2, 5, 4]).rolling_apply(range_func, 3).into(list)
213
+ [2, 3, 3]
214
+
215
+ ```
216
+ """
217
+ return self._lazy(rolling.Apply, window_size, "fixed", func)
218
+
219
+ def rolling_apply_pairwise[R](
220
+ self, other: Iterable[T], func: Callable[[T, T], R], window_size: int
221
+ ) -> Iter[R]:
222
+ """
223
+ Apply a custom pairwise function to each rolling window of size 2.
224
+
225
+ The function should accept two arguments and return a single value.
226
+
227
+ Args:
228
+ other: Second iterable to apply the pairwise function.
229
+ func: Function to apply to each pair of elements.
230
+ window_size: Size of the rolling window.
231
+ ```python
232
+ >>> import pyochain as pc
233
+ >>> from statistics import correlation as corr
234
+ >>> seq_1 = [1, 2, 3, 4, 5]
235
+ >>> seq_2 = [1, 2, 3, 2, 1]
236
+ >>> pc.Iter.from_(seq_1).rolling_apply_pairwise(seq_2, corr, 3).into(list)
237
+ [1.0, 0.0, -1.0]
238
+
239
+ ```
240
+ """
241
+ return self._lazy(rolling.ApplyPairwise, other, window_size, func)