@johnhenry/iteration 0.0.0
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.
- package/MIT-LICENSE.txt +20 -0
- package/dist/abort.d.ts +26 -0
- package/dist/abort.d.ts.map +1 -0
- package/dist/abort.js +69 -0
- package/dist/abort.js.map +1 -0
- package/dist/assertions/eventualequal.d.ts +9 -0
- package/dist/assertions/eventualequal.d.ts.map +1 -0
- package/dist/assertions/eventualequal.js +30 -0
- package/dist/assertions/eventualequal.js.map +1 -0
- package/dist/async-channel.d.ts +101 -0
- package/dist/async-channel.d.ts.map +1 -0
- package/dist/async-channel.js +208 -0
- package/dist/async-channel.js.map +1 -0
- package/dist/channel-decorators.d.ts +23 -0
- package/dist/channel-decorators.d.ts.map +1 -0
- package/dist/channel-decorators.js +31 -0
- package/dist/channel-decorators.js.map +1 -0
- package/dist/concurrency.d.ts +43 -0
- package/dist/concurrency.d.ts.map +1 -0
- package/dist/concurrency.js +145 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/consumers.d.ts +199 -0
- package/dist/consumers.d.ts.map +1 -0
- package/dist/consumers.js +349 -0
- package/dist/consumers.js.map +1 -0
- package/dist/count.d.ts +37 -0
- package/dist/count.d.ts.map +1 -0
- package/dist/count.js +70 -0
- package/dist/count.js.map +1 -0
- package/dist/empty-iterator.d.ts +15 -0
- package/dist/empty-iterator.d.ts.map +1 -0
- package/dist/empty-iterator.js +16 -0
- package/dist/empty-iterator.js.map +1 -0
- package/dist/eventualequal.d.ts +2 -0
- package/dist/eventualequal.d.ts.map +1 -0
- package/dist/eventualequal.js +54 -0
- package/dist/eventualequal.js.map +1 -0
- package/dist/exhaust.d.ts +7 -0
- package/dist/exhaust.d.ts.map +1 -0
- package/dist/exhaust.js +22 -0
- package/dist/exhaust.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/is-iterator.d.ts +5 -0
- package/dist/is-iterator.d.ts.map +1 -0
- package/dist/is-iterator.js +5 -0
- package/dist/is-iterator.js.map +1 -0
- package/dist/iterator-tools.d.ts +164 -0
- package/dist/iterator-tools.d.ts.map +1 -0
- package/dist/iterator-tools.js +231 -0
- package/dist/iterator-tools.js.map +1 -0
- package/dist/itertools.d.ts +237 -0
- package/dist/itertools.d.ts.map +1 -0
- package/dist/itertools.js +471 -0
- package/dist/itertools.js.map +1 -0
- package/dist/tee.d.ts +18 -0
- package/dist/tee.d.ts.map +1 -0
- package/dist/tee.js +118 -0
- package/dist/tee.js.map +1 -0
- package/dist/transduce.d.ts +79 -0
- package/dist/transduce.d.ts.map +1 -0
- package/dist/transduce.js +37 -0
- package/dist/transduce.js.map +1 -0
- package/dist/transducers.d.ts +123 -0
- package/dist/transducers.d.ts.map +1 -0
- package/dist/transducers.js +212 -0
- package/dist/transducers.js.map +1 -0
- package/package.json +122 -0
- package/readme.md +761 -0
- package/src/abort.ts +80 -0
- package/src/assertions/eventualequal.ts +36 -0
- package/src/async-channel.ts +228 -0
- package/src/channel-decorators.ts +49 -0
- package/src/concurrency.ts +168 -0
- package/src/consumers.ts +425 -0
- package/src/count.ts +72 -0
- package/src/empty-iterator.ts +17 -0
- package/src/eventualequal.ts +54 -0
- package/src/exhaust.ts +26 -0
- package/src/index.ts +17 -0
- package/src/is-iterator.ts +9 -0
- package/src/iterator-tools.ts +290 -0
- package/src/itertools.ts +518 -0
- package/src/tee.ts +136 -0
- package/src/transduce.ts +161 -0
- package/src/transducers.ts +256 -0
package/src/itertools.ts
ADDED
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python itertools-parity building blocks.
|
|
3
|
+
*
|
|
4
|
+
* Unlike the curried transducer factories in transducers.ts (which are
|
|
5
|
+
* shaped `(...) => (conjoin) => (init, item) => ...` for composition inside
|
|
6
|
+
* transduceSync/transduceAsync), the functions here are flat, direct
|
|
7
|
+
* iterable-in/iterable-out generators -- matching Python's itertools
|
|
8
|
+
* argument order (predicate/fn first, iterable(s) last) so porting a
|
|
9
|
+
* Python recipe is close to mechanical.
|
|
10
|
+
*
|
|
11
|
+
* Where a sync function's behavior is fully covered by native ES2025
|
|
12
|
+
* Iterator Helpers (Iterator.prototype.map/filter/...), it's implemented
|
|
13
|
+
* as a thin wrapper around Iterator.from(iterable) rather than a hand-rolled
|
|
14
|
+
* generator, to keep the maintenance surface small. Async counterparts are
|
|
15
|
+
* always hand-rolled async generators, since AsyncIterator helpers are not
|
|
16
|
+
* yet shipped (TC39 Stage 2).
|
|
17
|
+
*
|
|
18
|
+
* See docs/discussion/python-itertools.md for the full comparison to
|
|
19
|
+
* Python's itertools module and the documented design divergences.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { isAsyncIterator } from "./is-iterator.ts";
|
|
23
|
+
import { concatAsync, concatSync, zipAsync, zipSync } from "./iterator-tools.ts";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Yield items while predicate holds; stop (without consuming further) at
|
|
27
|
+
* the first item that fails it. Predicate-based complement to the
|
|
28
|
+
* numeric `take` transducer.
|
|
29
|
+
* @kind function
|
|
30
|
+
* @name takeWhileSync
|
|
31
|
+
*/
|
|
32
|
+
export function* takeWhileSync<T>(predicate: (item: T) => boolean, iterable: Iterable<T>): Generator<T> {
|
|
33
|
+
for (const item of iterable) {
|
|
34
|
+
if (!predicate(item)) return;
|
|
35
|
+
yield item;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Asynchronous dual of takeWhileSync.
|
|
41
|
+
* @kind function
|
|
42
|
+
* @name takeWhileAsync
|
|
43
|
+
*/
|
|
44
|
+
export async function* takeWhileAsync<T>(
|
|
45
|
+
predicate: (item: T) => boolean,
|
|
46
|
+
iterable: AsyncIterable<T> | Iterable<T>,
|
|
47
|
+
): AsyncGenerator<T> {
|
|
48
|
+
for await (const item of iterable) {
|
|
49
|
+
if (!predicate(item)) return;
|
|
50
|
+
yield item;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Drop items while predicate holds, then yield everything else.
|
|
56
|
+
* Predicate-based complement to the numeric `drop` transducer.
|
|
57
|
+
* @kind function
|
|
58
|
+
* @name dropWhileSync
|
|
59
|
+
*/
|
|
60
|
+
export function* dropWhileSync<T>(predicate: (item: T) => boolean, iterable: Iterable<T>): Generator<T> {
|
|
61
|
+
const it = iterable[Symbol.iterator]();
|
|
62
|
+
let r = it.next();
|
|
63
|
+
while (!r.done && predicate(r.value)) {
|
|
64
|
+
r = it.next();
|
|
65
|
+
}
|
|
66
|
+
while (!r.done) {
|
|
67
|
+
yield r.value;
|
|
68
|
+
r = it.next();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Asynchronous dual of dropWhileSync.
|
|
74
|
+
* @kind function
|
|
75
|
+
* @name dropWhileAsync
|
|
76
|
+
*/
|
|
77
|
+
export async function* dropWhileAsync<T>(
|
|
78
|
+
predicate: (item: T) => boolean,
|
|
79
|
+
iterable: AsyncIterable<T>,
|
|
80
|
+
): AsyncGenerator<T> {
|
|
81
|
+
const it = iterable[Symbol.asyncIterator]();
|
|
82
|
+
let r = await it.next();
|
|
83
|
+
while (!r.done && predicate(r.value)) {
|
|
84
|
+
r = await it.next();
|
|
85
|
+
}
|
|
86
|
+
while (!r.done) {
|
|
87
|
+
yield r.value;
|
|
88
|
+
r = await it.next();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Select items from `iterable` for which the parallel `selectors` iterable
|
|
94
|
+
* is truthy. Mirrors Python's itertools.compress(data, selectors).
|
|
95
|
+
* @kind function
|
|
96
|
+
* @name compressSync
|
|
97
|
+
*/
|
|
98
|
+
export function* compressSync<T>(iterable: Iterable<T>, selectors: Iterable<unknown>): Generator<T> {
|
|
99
|
+
for (const [item, keep] of zipSync<unknown>(iterable, selectors)) {
|
|
100
|
+
if (keep) yield item as T;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Asynchronous dual of compressSync.
|
|
106
|
+
* @kind function
|
|
107
|
+
* @name compressAsync
|
|
108
|
+
*/
|
|
109
|
+
export async function* compressAsync<T>(
|
|
110
|
+
iterable: AsyncIterable<T> | Iterable<T>,
|
|
111
|
+
selectors: AsyncIterable<unknown> | Iterable<unknown>,
|
|
112
|
+
): AsyncGenerator<T> {
|
|
113
|
+
for await (const [item, keep] of zipAsync<unknown>(iterable, selectors)) {
|
|
114
|
+
if (keep) yield item as T;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Yield overlapping windows of `n` consecutive items.
|
|
120
|
+
* @kind function
|
|
121
|
+
* @name windowedSync
|
|
122
|
+
*/
|
|
123
|
+
export function* windowedSync<T>(iterable: Iterable<T>, n: number): Generator<T[]> {
|
|
124
|
+
const buf: T[] = [];
|
|
125
|
+
for (const item of iterable) {
|
|
126
|
+
buf.push(item);
|
|
127
|
+
if (buf.length > n) buf.shift();
|
|
128
|
+
if (buf.length === n) yield buf.slice();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Asynchronous dual of windowedSync.
|
|
134
|
+
* @kind function
|
|
135
|
+
* @name windowedAsync
|
|
136
|
+
*/
|
|
137
|
+
export async function* windowedAsync<T>(iterable: AsyncIterable<T> | Iterable<T>, n: number): AsyncGenerator<T[]> {
|
|
138
|
+
const buf: T[] = [];
|
|
139
|
+
for await (const item of iterable) {
|
|
140
|
+
buf.push(item);
|
|
141
|
+
if (buf.length > n) buf.shift();
|
|
142
|
+
if (buf.length === n) yield buf.slice();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Yield successive overlapping pairs. Mirrors Python 3.10+'s
|
|
148
|
+
* itertools.pairwise -- a thin call to windowedSync(iterable, 2) so the
|
|
149
|
+
* ring-buffer logic isn't duplicated.
|
|
150
|
+
* @kind function
|
|
151
|
+
* @name pairwiseSync
|
|
152
|
+
*/
|
|
153
|
+
export function* pairwiseSync<T>(iterable: Iterable<T>): Generator<[T, T]> {
|
|
154
|
+
yield* windowedSync(iterable, 2) as Generator<[T, T]>;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Asynchronous dual of pairwiseSync.
|
|
159
|
+
* @kind function
|
|
160
|
+
* @name pairwiseAsync
|
|
161
|
+
*/
|
|
162
|
+
export async function* pairwiseAsync<T>(iterable: AsyncIterable<T> | Iterable<T>): AsyncGenerator<[T, T]> {
|
|
163
|
+
yield* windowedAsync(iterable, 2) as AsyncGenerator<[T, T]>;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Group consecutive items sharing a key into `[key, items[]]` pairs.
|
|
168
|
+
* Mirrors Python's itertools.groupby, but deliberately diverges from it:
|
|
169
|
+
* Python's sub-iterators are lazy and invalidated by the next outer
|
|
170
|
+
* `next()` call (a well-known footgun); this eagerly materializes each
|
|
171
|
+
* run as an array instead, which is safer and matches what JS developers
|
|
172
|
+
* expect. As in Python, only *consecutive* runs are grouped -- sort the
|
|
173
|
+
* input first if you want all occurrences of a key merged globally.
|
|
174
|
+
* @kind function
|
|
175
|
+
* @name groupBySync
|
|
176
|
+
*/
|
|
177
|
+
export function* groupBySync<T, K = T>(
|
|
178
|
+
iterable: Iterable<T>,
|
|
179
|
+
keyFn: (item: T) => K = (x) => x as unknown as K,
|
|
180
|
+
): Generator<[K, T[]]> {
|
|
181
|
+
const it = iterable[Symbol.iterator]();
|
|
182
|
+
let r = it.next();
|
|
183
|
+
if (r.done) return;
|
|
184
|
+
let currentKey = keyFn(r.value);
|
|
185
|
+
let bucket = [r.value];
|
|
186
|
+
for (r = it.next(); !r.done; r = it.next()) {
|
|
187
|
+
const key = keyFn(r.value);
|
|
188
|
+
if (Object.is(key, currentKey)) {
|
|
189
|
+
bucket.push(r.value);
|
|
190
|
+
} else {
|
|
191
|
+
yield [currentKey, bucket];
|
|
192
|
+
currentKey = key;
|
|
193
|
+
bucket = [r.value];
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
yield [currentKey, bucket];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Asynchronous dual of groupBySync.
|
|
201
|
+
* @kind function
|
|
202
|
+
* @name groupByAsync
|
|
203
|
+
*/
|
|
204
|
+
export async function* groupByAsync<T, K = T>(
|
|
205
|
+
iterable: AsyncIterable<T>,
|
|
206
|
+
keyFn: (item: T) => K = (x) => x as unknown as K,
|
|
207
|
+
): AsyncGenerator<[K, T[]]> {
|
|
208
|
+
const it = iterable[Symbol.asyncIterator]();
|
|
209
|
+
let r = await it.next();
|
|
210
|
+
if (r.done) return;
|
|
211
|
+
let currentKey = keyFn(r.value);
|
|
212
|
+
let bucket = [r.value];
|
|
213
|
+
for (r = await it.next(); !r.done; r = await it.next()) {
|
|
214
|
+
const key = keyFn(r.value);
|
|
215
|
+
if (Object.is(key, currentKey)) {
|
|
216
|
+
bucket.push(r.value);
|
|
217
|
+
} else {
|
|
218
|
+
yield [currentKey, bucket];
|
|
219
|
+
currentKey = key;
|
|
220
|
+
bucket = [r.value];
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
yield [currentKey, bucket];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Chain multiple iterables into one sequence. Mirrors Python's
|
|
228
|
+
* itertools.chain(*iterables); an itertools-parity alias of concatSync.
|
|
229
|
+
* @kind function
|
|
230
|
+
* @name chainSync
|
|
231
|
+
* @see flattenSync
|
|
232
|
+
*/
|
|
233
|
+
export const chainSync = concatSync;
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Asynchronous dual of chainSync; alias of concatAsync.
|
|
237
|
+
* @kind function
|
|
238
|
+
* @name chainAsync
|
|
239
|
+
* @see flattenAsync
|
|
240
|
+
*/
|
|
241
|
+
export const chainAsync = concatAsync;
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Flatten one level of an iterable-of-iterables. Mirrors Python's
|
|
245
|
+
* itertools.chain.from_iterable -- the single-arg counterpart to
|
|
246
|
+
* chainSync's variadic form. Not recursive.
|
|
247
|
+
* @kind function
|
|
248
|
+
* @name flattenSync
|
|
249
|
+
* @see chainSync
|
|
250
|
+
*/
|
|
251
|
+
export function* flattenSync<T>(iterableOfIterables: Iterable<Iterable<T>>): Generator<T> {
|
|
252
|
+
for (const inner of iterableOfIterables) {
|
|
253
|
+
yield* inner;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Asynchronous dual of flattenSync.
|
|
259
|
+
* @kind function
|
|
260
|
+
* @name flattenAsync
|
|
261
|
+
* @see chainAsync
|
|
262
|
+
*/
|
|
263
|
+
export async function* flattenAsync<T>(
|
|
264
|
+
iterableOfIterables: AsyncIterable<AsyncIterable<T> | Iterable<T>> | Iterable<AsyncIterable<T> | Iterable<T>>,
|
|
265
|
+
): AsyncGenerator<T> {
|
|
266
|
+
for await (const inner of iterableOfIterables) {
|
|
267
|
+
yield* inner;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Cycle through `iterable` forever, buffering it on the first pass so a
|
|
273
|
+
* one-shot source (e.g. a generator) can still be replayed. Yields
|
|
274
|
+
* nothing if the source is empty.
|
|
275
|
+
* @kind function
|
|
276
|
+
* @name cycleSync
|
|
277
|
+
*/
|
|
278
|
+
export function* cycleSync<T>(iterable: Iterable<T>): Generator<T> {
|
|
279
|
+
const buffer: T[] = [];
|
|
280
|
+
for (const item of iterable) {
|
|
281
|
+
buffer.push(item);
|
|
282
|
+
yield item;
|
|
283
|
+
}
|
|
284
|
+
while (buffer.length > 0) {
|
|
285
|
+
yield* buffer;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Asynchronous dual of cycleSync.
|
|
291
|
+
* @kind function
|
|
292
|
+
* @name cycleAsync
|
|
293
|
+
*/
|
|
294
|
+
export async function* cycleAsync<T>(iterable: AsyncIterable<T> | Iterable<T>): AsyncGenerator<T> {
|
|
295
|
+
const buffer: T[] = [];
|
|
296
|
+
for await (const item of iterable) {
|
|
297
|
+
buffer.push(item);
|
|
298
|
+
yield item;
|
|
299
|
+
}
|
|
300
|
+
while (buffer.length > 0) {
|
|
301
|
+
yield* buffer;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Yield `value` repeatedly, `times` times (default infinite).
|
|
307
|
+
* @kind function
|
|
308
|
+
* @name repeatSync
|
|
309
|
+
*/
|
|
310
|
+
export function* repeatSync<T>(value: T, times = Infinity): Generator<T> {
|
|
311
|
+
for (let i = 0; i < times; i++) {
|
|
312
|
+
yield value;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Asynchronous dual of repeatSync.
|
|
318
|
+
* @kind function
|
|
319
|
+
* @name repeatAsync
|
|
320
|
+
*/
|
|
321
|
+
export async function* repeatAsync<T>(value: T, times = Infinity): AsyncGenerator<T> {
|
|
322
|
+
for (let i = 0; i < times; i++) {
|
|
323
|
+
yield value;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Yield only the first occurrence of each key -- global, unbounded-memory
|
|
329
|
+
* dedupe. Mirrors the `unique_everseen` recipe from Python's itertools docs.
|
|
330
|
+
* @kind function
|
|
331
|
+
* @name uniqueSync
|
|
332
|
+
*/
|
|
333
|
+
export function* uniqueSync<T, K = T>(
|
|
334
|
+
iterable: Iterable<T>,
|
|
335
|
+
keyFn: (item: T) => K = (x) => x as unknown as K,
|
|
336
|
+
): Generator<T> {
|
|
337
|
+
const seen = new Set<K>();
|
|
338
|
+
for (const item of iterable) {
|
|
339
|
+
const key = keyFn(item);
|
|
340
|
+
if (!seen.has(key)) {
|
|
341
|
+
seen.add(key);
|
|
342
|
+
yield item;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Asynchronous dual of uniqueSync.
|
|
349
|
+
* @kind function
|
|
350
|
+
* @name uniqueAsync
|
|
351
|
+
*/
|
|
352
|
+
export async function* uniqueAsync<T, K = T>(
|
|
353
|
+
iterable: AsyncIterable<T> | Iterable<T>,
|
|
354
|
+
keyFn: (item: T) => K = (x) => x as unknown as K,
|
|
355
|
+
): AsyncGenerator<T> {
|
|
356
|
+
const seen = new Set<K>();
|
|
357
|
+
for await (const item of iterable) {
|
|
358
|
+
const key = keyFn(item);
|
|
359
|
+
if (!seen.has(key)) {
|
|
360
|
+
seen.add(key);
|
|
361
|
+
yield item;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Yield `[index, value]` pairs, mirroring Python's builtin enumerate().
|
|
368
|
+
* Implemented via native Iterator Helpers: Iterator.prototype.map's
|
|
369
|
+
* callback receives `(value, index)`, unlike Array.prototype.map's
|
|
370
|
+
* `(value, index, array)` -- confirmed present in ES2025.
|
|
371
|
+
* @kind function
|
|
372
|
+
* @name enumerateSync
|
|
373
|
+
*/
|
|
374
|
+
export function* enumerateSync<T>(iterable: Iterable<T>, start = 0): Generator<[number, T]> {
|
|
375
|
+
yield* Iterator.from(iterable).map((value, index): [number, T] => [index + start, value]);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Asynchronous dual of enumerateSync (hand-rolled -- no native
|
|
380
|
+
* AsyncIterator helpers are shipped yet).
|
|
381
|
+
* @kind function
|
|
382
|
+
* @name enumerateAsync
|
|
383
|
+
*/
|
|
384
|
+
export async function* enumerateAsync<T>(
|
|
385
|
+
iterable: AsyncIterable<T> | Iterable<T>,
|
|
386
|
+
start = 0,
|
|
387
|
+
): AsyncGenerator<[number, T]> {
|
|
388
|
+
let index = start;
|
|
389
|
+
for await (const value of iterable) {
|
|
390
|
+
yield [index++, value];
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Call `fn` with each item's contents spread as arguments. Mirrors
|
|
396
|
+
* Python's itertools.starmap.
|
|
397
|
+
* @kind function
|
|
398
|
+
* @name starmapSync
|
|
399
|
+
*/
|
|
400
|
+
export function* starmapSync<Args extends unknown[], R>(
|
|
401
|
+
fn: (...args: Args) => R,
|
|
402
|
+
iterableOfArgArrays: Iterable<Args>,
|
|
403
|
+
): Generator<R> {
|
|
404
|
+
yield* Iterator.from(iterableOfArgArrays).map((args) => fn(...args));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Asynchronous dual of starmapSync.
|
|
409
|
+
* @kind function
|
|
410
|
+
* @name starmapAsync
|
|
411
|
+
*/
|
|
412
|
+
export async function* starmapAsync<Args extends unknown[], R>(
|
|
413
|
+
fn: (...args: Args) => R,
|
|
414
|
+
iterableOfArgArrays: AsyncIterable<Args> | Iterable<Args>,
|
|
415
|
+
): AsyncGenerator<R> {
|
|
416
|
+
for await (const args of iterableOfArgArrays) {
|
|
417
|
+
yield fn(...args);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Zip iterators, continuing to the longest one and filling exhausted
|
|
423
|
+
* ones with `fillValue`. Complement to the strict, stop-at-shortest
|
|
424
|
+
* zipSync/zipAsync. Mirrors Python's itertools.zip_longest.
|
|
425
|
+
* @kind function
|
|
426
|
+
* @name zipLongestSync
|
|
427
|
+
* @param fillValue value used once an input is exhausted
|
|
428
|
+
* @param iteratorList iterables to zip
|
|
429
|
+
*/
|
|
430
|
+
export function* zipLongestSync<T, F>(fillValue: F, ...iteratorList: Array<Iterable<T>>): Generator<Array<T | F>> {
|
|
431
|
+
const generators = iteratorList.map((iterator) => iterator[Symbol.iterator]());
|
|
432
|
+
while (true) {
|
|
433
|
+
const result: Array<T | F> = [];
|
|
434
|
+
let anyNotDone = false;
|
|
435
|
+
for (const generator of generators) {
|
|
436
|
+
const { value, done } = generator.next();
|
|
437
|
+
if (done) {
|
|
438
|
+
result.push(fillValue);
|
|
439
|
+
} else {
|
|
440
|
+
anyNotDone = true;
|
|
441
|
+
result.push(value);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (!anyNotDone) return;
|
|
445
|
+
yield result;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Asynchronous dual of zipLongestSync. Pulls the next value from every
|
|
451
|
+
* input in parallel each round, like zipAsync.
|
|
452
|
+
* @kind function
|
|
453
|
+
* @name zipLongestAsync
|
|
454
|
+
*/
|
|
455
|
+
export async function* zipLongestAsync<T, F>(
|
|
456
|
+
fillValue: F,
|
|
457
|
+
...iteratorList: Array<AsyncIterable<T> | Iterable<T>>
|
|
458
|
+
): AsyncGenerator<Array<T | F>> {
|
|
459
|
+
const generators = iteratorList.map((iterator) =>
|
|
460
|
+
isAsyncIterator(iterator)
|
|
461
|
+
? (iterator as AsyncIterable<T>)[Symbol.asyncIterator]()
|
|
462
|
+
: (iterator as Iterable<T>)[Symbol.iterator](),
|
|
463
|
+
);
|
|
464
|
+
while (true) {
|
|
465
|
+
const results = await Promise.all(generators.map((g) => g.next()));
|
|
466
|
+
if (results.every(({ done }) => done)) return;
|
|
467
|
+
yield results.map(({ value, done }) => (done ? fillValue : (value as T)));
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* General slicing over an iterable. Mirrors Python's itertools.islice
|
|
473
|
+
* overloads: `isliceSync(iterable, stop)` or
|
|
474
|
+
* `isliceSync(iterable, start, stop, step)`. `step` has no native
|
|
475
|
+
* fast-path, so both sync and async are hand-rolled uniformly rather
|
|
476
|
+
* than branching between a native-composed common case and a fallback.
|
|
477
|
+
* @kind function
|
|
478
|
+
* @name isliceSync
|
|
479
|
+
*/
|
|
480
|
+
export function* isliceSync<T>(iterable: Iterable<T>, ...args: Array<number | null | undefined>): Generator<T> {
|
|
481
|
+
let start = 0,
|
|
482
|
+
stop: number | null | undefined = Infinity,
|
|
483
|
+
step = 1;
|
|
484
|
+
if (args.length === 1) [stop] = args;
|
|
485
|
+
else if (args.length === 2) [start, stop] = args as [number, number];
|
|
486
|
+
else if (args.length >= 3) [start, stop, step] = args as [number, number, number];
|
|
487
|
+
if (stop == null) stop = Infinity;
|
|
488
|
+
if (step < 1) throw new RangeError("islice step must be >= 1");
|
|
489
|
+
const it = iterable[Symbol.iterator]();
|
|
490
|
+
let i = 0;
|
|
491
|
+
for (let r = it.next(); !r.done && i < stop; r = it.next(), i++) {
|
|
492
|
+
if (i >= start && (i - start) % step === 0) yield r.value;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Asynchronous dual of isliceSync.
|
|
498
|
+
* @kind function
|
|
499
|
+
* @name isliceAsync
|
|
500
|
+
*/
|
|
501
|
+
export async function* isliceAsync<T>(
|
|
502
|
+
iterable: AsyncIterable<T>,
|
|
503
|
+
...args: Array<number | null | undefined>
|
|
504
|
+
): AsyncGenerator<T> {
|
|
505
|
+
let start = 0,
|
|
506
|
+
stop: number | null | undefined = Infinity,
|
|
507
|
+
step = 1;
|
|
508
|
+
if (args.length === 1) [stop] = args;
|
|
509
|
+
else if (args.length === 2) [start, stop] = args as [number, number];
|
|
510
|
+
else if (args.length >= 3) [start, stop, step] = args as [number, number, number];
|
|
511
|
+
if (stop == null) stop = Infinity;
|
|
512
|
+
if (step < 1) throw new RangeError("islice step must be >= 1");
|
|
513
|
+
const it = iterable[Symbol.asyncIterator]();
|
|
514
|
+
let i = 0;
|
|
515
|
+
for (let r = await it.next(); !r.done && i < stop; r = await it.next(), i++) {
|
|
516
|
+
if (i >= start && (i - start) % step === 0) yield r.value;
|
|
517
|
+
}
|
|
518
|
+
}
|
package/src/tee.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a function that tees emitted items to n iterators
|
|
3
|
+
* @kind function
|
|
4
|
+
* @name teeSync / teeAsync
|
|
5
|
+
* @param num number of iterators to create
|
|
6
|
+
* @returns function
|
|
7
|
+
* @example <caption>Split an iterator into 4 </caption>
|
|
8
|
+
* ```javascript
|
|
9
|
+
* import { teeAsync, countAsync } from '@johnhenry/iteration';
|
|
10
|
+
* const streams = teeAsync(4)(countAsync(Infinity))
|
|
11
|
+
* for await (const num of streams[0]){
|
|
12
|
+
* console.info(num);
|
|
13
|
+
* };
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const DONE = Symbol("DONE");
|
|
18
|
+
|
|
19
|
+
export const teeSync =
|
|
20
|
+
(num = 0) =>
|
|
21
|
+
<T>(iterable: Iterable<T>): Array<Generator<T>> => {
|
|
22
|
+
const source = iterable[Symbol.iterator]();
|
|
23
|
+
const buffers: T[][] = new Array(num).fill(null).map(() => []);
|
|
24
|
+
|
|
25
|
+
const next = (i: number): T | typeof DONE => {
|
|
26
|
+
const buffer = buffers[i] as T[];
|
|
27
|
+
if (buffer.length !== 0) {
|
|
28
|
+
return buffer.shift() as T;
|
|
29
|
+
}
|
|
30
|
+
const x = source.next();
|
|
31
|
+
|
|
32
|
+
if (x.done) {
|
|
33
|
+
return DONE;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (let j = 0; j < buffers.length; j++) {
|
|
37
|
+
if (j !== i) (buffers[j] as T[]).push(x.value);
|
|
38
|
+
}
|
|
39
|
+
return x.value;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return buffers.map(function* (_, i) {
|
|
43
|
+
try {
|
|
44
|
+
for (;;) {
|
|
45
|
+
const x = next(i);
|
|
46
|
+
|
|
47
|
+
if (x === DONE) {
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
yield x;
|
|
52
|
+
}
|
|
53
|
+
} finally {
|
|
54
|
+
// Runs on natural completion *and* on early exit (e.g. a consumer
|
|
55
|
+
// `break`s out of a `for...of`, which calls this generator's own
|
|
56
|
+
// .return(), which runs this finally block) -- without it, an
|
|
57
|
+
// early-exiting consumer left the shared source iterator (and
|
|
58
|
+
// whatever resource it holds) never closed.
|
|
59
|
+
source.return?.();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const teeAsync =
|
|
65
|
+
(num = 0) =>
|
|
66
|
+
<T>(iterable: AsyncIterable<T>): Array<AsyncGenerator<T>> => {
|
|
67
|
+
const source = iterable[Symbol.asyncIterator]();
|
|
68
|
+
const buffers: T[][] = new Array(num).fill(null).map(() => []);
|
|
69
|
+
|
|
70
|
+
// Guards against the check-then-act race between "is my buffer empty"
|
|
71
|
+
// and "pull from source": if two branches both call next() while their
|
|
72
|
+
// buffers are empty in the same logical turn, only the first should
|
|
73
|
+
// actually call source.next(); the rest must ride along on that single
|
|
74
|
+
// in-flight pull and then re-check their (now-populated) buffer, rather
|
|
75
|
+
// than each independently issuing their own source.next() call.
|
|
76
|
+
let inFlight: Promise<T | typeof DONE> | null = null;
|
|
77
|
+
let sourceDone = false;
|
|
78
|
+
|
|
79
|
+
const next = async (i: number): Promise<T | typeof DONE> => {
|
|
80
|
+
const buffer = buffers[i] as T[];
|
|
81
|
+
if (buffer.length !== 0) {
|
|
82
|
+
return buffer.shift() as T;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (sourceDone) {
|
|
86
|
+
return DONE;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (inFlight) {
|
|
90
|
+
// Someone else is already pulling for this turn -- wait for it, then
|
|
91
|
+
// re-evaluate: our buffer will have been fed by the puller (unless
|
|
92
|
+
// the source is now exhausted).
|
|
93
|
+
await inFlight;
|
|
94
|
+
return next(i);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const pull = (async (): Promise<T | typeof DONE> => {
|
|
98
|
+
const x = await source.next();
|
|
99
|
+
|
|
100
|
+
if (x.done) {
|
|
101
|
+
sourceDone = true;
|
|
102
|
+
return DONE;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (let j = 0; j < buffers.length; j++) {
|
|
106
|
+
if (j !== i) (buffers[j] as T[]).push(x.value);
|
|
107
|
+
}
|
|
108
|
+
return x.value;
|
|
109
|
+
})();
|
|
110
|
+
|
|
111
|
+
inFlight = pull;
|
|
112
|
+
try {
|
|
113
|
+
return await pull;
|
|
114
|
+
} finally {
|
|
115
|
+
inFlight = null;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return buffers.map(async function* (_, i) {
|
|
120
|
+
try {
|
|
121
|
+
for (;;) {
|
|
122
|
+
const x = await next(i);
|
|
123
|
+
|
|
124
|
+
if (x === DONE) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
yield x;
|
|
129
|
+
}
|
|
130
|
+
} finally {
|
|
131
|
+
// See teeSync's matching finally: runs on natural completion *and*
|
|
132
|
+
// on early exit, so the shared source iterator is always closed.
|
|
133
|
+
await source.return?.();
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
};
|