omlish 0.0.0.dev164__py3-none-any.whl → 0.0.0.dev166__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
omlish/iterators.py DELETED
@@ -1,300 +0,0 @@
1
- import collections
2
- import dataclasses as dc
3
- import functools
4
- import heapq
5
- import itertools
6
- import typing as ta
7
-
8
- # from . import check
9
- from . import lang
10
-
11
-
12
- T = ta.TypeVar('T')
13
- U = ta.TypeVar('U')
14
-
15
- _MISSING = object()
16
-
17
-
18
- class PeekIterator(ta.Iterator[T]):
19
-
20
- def __init__(self, it: ta.Iterable[T]) -> None:
21
- super().__init__()
22
-
23
- self._it = iter(it)
24
- self._pos = -1
25
- self._next_item: ta.Any = _MISSING
26
-
27
- _item: T
28
-
29
- def __iter__(self) -> ta.Self:
30
- return self
31
-
32
- @property
33
- def done(self) -> bool:
34
- try:
35
- self.peek()
36
- except StopIteration:
37
- return True
38
- else:
39
- return False
40
-
41
- def __next__(self) -> T:
42
- if self._next_item is not _MISSING:
43
- self._item = ta.cast(T, self._next_item)
44
- self._next_item = _MISSING
45
- else:
46
- self._item = next(self._it)
47
- self._pos += 1
48
- return self._item
49
-
50
- def peek(self) -> T:
51
- if self._next_item is not _MISSING:
52
- return ta.cast(T, self._next_item)
53
- self._next_item = next(self._it)
54
- return self._next_item
55
-
56
- def next_peek(self) -> T:
57
- next(self)
58
- return self.peek()
59
-
60
- def takewhile(self, fn: ta.Callable[[T], bool]) -> ta.Iterator[T]:
61
- while fn(self.peek()):
62
- yield next(self)
63
-
64
- def skipwhile(self, fn: ta.Callable[[T], bool]) -> None:
65
- while fn(self.peek()):
66
- next(self)
67
-
68
- def takeuntil(self, fn: ta.Callable[[T], bool]) -> ta.Iterator[T]:
69
- return self.takewhile(lambda e: not fn(e))
70
-
71
- def skipuntil(self, fn: ta.Callable[[T], bool]) -> None:
72
- self.skipwhile(lambda e: not fn(e))
73
-
74
- def takethrough(self, pos: int) -> ta.Iterator[T]:
75
- return self.takewhile(lambda _: self._pos < pos)
76
-
77
- def skipthrough(self, pos: int) -> None:
78
- self.skipwhile(lambda _: self._pos < pos)
79
-
80
- def taketo(self, pos: int) -> ta.Iterator[T]:
81
- return self.takethrough(pos - 1)
82
-
83
- def skipto(self, pos: int) -> None:
84
- self.skipthrough(pos - 1)
85
-
86
-
87
- class ProxyIterator(ta.Iterator[T]):
88
-
89
- def __init__(self, fn: ta.Callable[[], T]) -> None:
90
- self._fn = fn
91
-
92
- def __iter__(self) -> ta.Self:
93
- return self
94
-
95
- def __next__(self) -> T:
96
- return self._fn()
97
-
98
-
99
- class PrefetchIterator(ta.Iterator[T]):
100
-
101
- def __init__(self, fn: ta.Callable[[], T] | None = None) -> None:
102
- super().__init__()
103
-
104
- self._fn = fn
105
- self._deque: collections.deque[T] = collections.deque()
106
-
107
- def __iter__(self) -> ta.Self:
108
- return self
109
-
110
- def push(self, item) -> None:
111
- self._deque.append(item)
112
-
113
- def __next__(self) -> T:
114
- try:
115
- return self._deque.popleft()
116
- except IndexError:
117
- if self._fn is None:
118
- raise StopIteration from None
119
- return self._fn()
120
-
121
-
122
- class RetainIterator(ta.Iterator[T]):
123
-
124
- def __init__(self, fn: ta.Callable[[], T]) -> None:
125
- super().__init__()
126
-
127
- self._fn = fn
128
- self._deque: collections.deque[T] = collections.deque()
129
-
130
- def __iter__(self) -> ta.Self:
131
- return self
132
-
133
- def pop(self) -> None:
134
- self._deque.popleft()
135
-
136
- def __next__(self) -> T:
137
- item = self._fn()
138
- self._deque.append(item)
139
- return item
140
-
141
-
142
- def unzip(it: ta.Iterable[T], width: int | None = None) -> list:
143
- if width is None:
144
- if not isinstance(it, PeekIterator):
145
- it = PeekIterator(iter(it))
146
- try:
147
- width = len(it.peek())
148
- except StopIteration:
149
- return []
150
-
151
- its: list[PrefetchIterator[T]] = []
152
- running = True
153
-
154
- def next_fn(idx):
155
- nonlocal running
156
- if not running:
157
- raise StopIteration
158
- try:
159
- items = next(it) # type: ignore
160
- except StopIteration:
161
- running = False
162
- raise
163
- for item_idx, item in enumerate(items):
164
- its[item_idx].push(item)
165
- return next(its[idx])
166
-
167
- its.extend(PrefetchIterator(functools.partial(next_fn, idx)) for idx in range(width))
168
- return its
169
-
170
-
171
- def take(n: int, iterable: ta.Iterable[T]) -> list[T]:
172
- return list(itertools.islice(iterable, n))
173
-
174
-
175
- def chunk(n: int, iterable: ta.Iterable[T], strict: bool = False) -> ta.Iterator[list[T]]:
176
- iterator = iter(functools.partial(take, n, iter(iterable)), [])
177
- if strict:
178
- def ret():
179
- for chunk in iterator:
180
- if len(chunk) != n:
181
- raise ValueError('iterable is not divisible by n.')
182
- yield chunk
183
- return iter(ret())
184
- else:
185
- return iterator
186
-
187
-
188
- def merge_on(
189
- function: ta.Callable[[T], U],
190
- *its: ta.Iterable[T],
191
- ) -> ta.Iterator[tuple[U, list[tuple[int, T]]]]:
192
- indexed_its = [
193
- (
194
- (function(item), it_idx, item)
195
- for it_idx, item in zip(itertools.repeat(it_idx), it)
196
- )
197
- for it_idx, it in enumerate(its)
198
- ]
199
-
200
- grouped_indexed_its = itertools.groupby(
201
- heapq.merge(*indexed_its),
202
- key=lambda item_tuple: item_tuple[0],
203
- )
204
-
205
- return (
206
- (fn_item, [(it_idx, item) for _, it_idx, item in grp])
207
- for fn_item, grp in grouped_indexed_its
208
- )
209
-
210
-
211
- def expand_indexed_pairs(
212
- seq: ta.Iterable[tuple[int, T]],
213
- default: T,
214
- *,
215
- width: int | None = None,
216
- ) -> list[T]:
217
- width_ = width
218
- if width_ is None:
219
- width_ = (max(idx for idx, _ in seq) + 1) if seq else 0
220
- result = [default] * width_
221
- for idx, value in seq:
222
- if idx < width_:
223
- result[idx] = value
224
- return result
225
-
226
-
227
- ##
228
- # https://docs.python.org/3/library/itertools.html#itertools-recipes
229
-
230
-
231
- def sliding_window(it: ta.Iterable[T], n: int) -> ta.Iterator[tuple[T, ...]]:
232
- # sliding_window('ABCDEFG', 4) -> ABCD BCDE CDEF DEFG
233
- iterator = iter(it)
234
- window = collections.deque(itertools.islice(iterator, n - 1), maxlen=n)
235
- for x in iterator:
236
- window.append(x)
237
- yield tuple(window)
238
-
239
-
240
- ##
241
-
242
-
243
- @dc.dataclass()
244
- class UniqueStats:
245
- key: ta.Any
246
- num_seen: int
247
- first_idx: int
248
- last_idx: int
249
-
250
-
251
- @dc.dataclass(frozen=True)
252
- class UniqueItem(ta.Generic[T]):
253
- idx: int
254
- item: T
255
- stats: UniqueStats
256
- out: lang.Maybe[T]
257
-
258
-
259
- class UniqueIterator(ta.Iterator[UniqueItem[T]]):
260
- def __init__(
261
- self,
262
- it: ta.Iterable[T],
263
- keyer: ta.Callable[[T], ta.Any] = lang.identity,
264
- ) -> None:
265
- super().__init__()
266
- self._it = enumerate(it)
267
- self._keyer = keyer
268
-
269
- self.stats: dict[ta.Any, UniqueStats] = {}
270
-
271
- def __next__(self) -> UniqueItem[T]:
272
- idx, item = next(self._it)
273
- key = self._keyer(item)
274
-
275
- try:
276
- stats = self.stats[key]
277
-
278
- except KeyError:
279
- stats = self.stats[key] = UniqueStats(
280
- key,
281
- num_seen=1,
282
- first_idx=idx,
283
- last_idx=idx,
284
- )
285
- return UniqueItem(
286
- idx,
287
- item,
288
- stats,
289
- lang.just(item),
290
- )
291
-
292
- else:
293
- stats.num_seen += 1
294
- stats.last_idx = idx
295
- return UniqueItem(
296
- idx,
297
- item,
298
- stats,
299
- lang.empty(),
300
- )