pyochain 0.5.0__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.

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