omlish 0.0.0.dev164__py3-none-any.whl → 0.0.0.dev166__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.
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
- )