@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.
Files changed (88) hide show
  1. package/MIT-LICENSE.txt +20 -0
  2. package/dist/abort.d.ts +26 -0
  3. package/dist/abort.d.ts.map +1 -0
  4. package/dist/abort.js +69 -0
  5. package/dist/abort.js.map +1 -0
  6. package/dist/assertions/eventualequal.d.ts +9 -0
  7. package/dist/assertions/eventualequal.d.ts.map +1 -0
  8. package/dist/assertions/eventualequal.js +30 -0
  9. package/dist/assertions/eventualequal.js.map +1 -0
  10. package/dist/async-channel.d.ts +101 -0
  11. package/dist/async-channel.d.ts.map +1 -0
  12. package/dist/async-channel.js +208 -0
  13. package/dist/async-channel.js.map +1 -0
  14. package/dist/channel-decorators.d.ts +23 -0
  15. package/dist/channel-decorators.d.ts.map +1 -0
  16. package/dist/channel-decorators.js +31 -0
  17. package/dist/channel-decorators.js.map +1 -0
  18. package/dist/concurrency.d.ts +43 -0
  19. package/dist/concurrency.d.ts.map +1 -0
  20. package/dist/concurrency.js +145 -0
  21. package/dist/concurrency.js.map +1 -0
  22. package/dist/consumers.d.ts +199 -0
  23. package/dist/consumers.d.ts.map +1 -0
  24. package/dist/consumers.js +349 -0
  25. package/dist/consumers.js.map +1 -0
  26. package/dist/count.d.ts +37 -0
  27. package/dist/count.d.ts.map +1 -0
  28. package/dist/count.js +70 -0
  29. package/dist/count.js.map +1 -0
  30. package/dist/empty-iterator.d.ts +15 -0
  31. package/dist/empty-iterator.d.ts.map +1 -0
  32. package/dist/empty-iterator.js +16 -0
  33. package/dist/empty-iterator.js.map +1 -0
  34. package/dist/eventualequal.d.ts +2 -0
  35. package/dist/eventualequal.d.ts.map +1 -0
  36. package/dist/eventualequal.js +54 -0
  37. package/dist/eventualequal.js.map +1 -0
  38. package/dist/exhaust.d.ts +7 -0
  39. package/dist/exhaust.d.ts.map +1 -0
  40. package/dist/exhaust.js +22 -0
  41. package/dist/exhaust.js.map +1 -0
  42. package/dist/index.d.ts +16 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +16 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/is-iterator.d.ts +5 -0
  47. package/dist/is-iterator.d.ts.map +1 -0
  48. package/dist/is-iterator.js +5 -0
  49. package/dist/is-iterator.js.map +1 -0
  50. package/dist/iterator-tools.d.ts +164 -0
  51. package/dist/iterator-tools.d.ts.map +1 -0
  52. package/dist/iterator-tools.js +231 -0
  53. package/dist/iterator-tools.js.map +1 -0
  54. package/dist/itertools.d.ts +237 -0
  55. package/dist/itertools.d.ts.map +1 -0
  56. package/dist/itertools.js +471 -0
  57. package/dist/itertools.js.map +1 -0
  58. package/dist/tee.d.ts +18 -0
  59. package/dist/tee.d.ts.map +1 -0
  60. package/dist/tee.js +118 -0
  61. package/dist/tee.js.map +1 -0
  62. package/dist/transduce.d.ts +79 -0
  63. package/dist/transduce.d.ts.map +1 -0
  64. package/dist/transduce.js +37 -0
  65. package/dist/transduce.js.map +1 -0
  66. package/dist/transducers.d.ts +123 -0
  67. package/dist/transducers.d.ts.map +1 -0
  68. package/dist/transducers.js +212 -0
  69. package/dist/transducers.js.map +1 -0
  70. package/package.json +122 -0
  71. package/readme.md +761 -0
  72. package/src/abort.ts +80 -0
  73. package/src/assertions/eventualequal.ts +36 -0
  74. package/src/async-channel.ts +228 -0
  75. package/src/channel-decorators.ts +49 -0
  76. package/src/concurrency.ts +168 -0
  77. package/src/consumers.ts +425 -0
  78. package/src/count.ts +72 -0
  79. package/src/empty-iterator.ts +17 -0
  80. package/src/eventualequal.ts +54 -0
  81. package/src/exhaust.ts +26 -0
  82. package/src/index.ts +17 -0
  83. package/src/is-iterator.ts +9 -0
  84. package/src/iterator-tools.ts +290 -0
  85. package/src/itertools.ts +518 -0
  86. package/src/tee.ts +136 -0
  87. package/src/transduce.ts +161 -0
  88. package/src/transducers.ts +256 -0
@@ -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
+ };