@johnhenry/iteration 0.0.0 → 0.1.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/dist/async-channel.d.ts +24 -4
- package/dist/async-channel.d.ts.map +1 -1
- package/dist/async-channel.js +41 -38
- package/dist/async-channel.js.map +1 -1
- package/dist/combine.d.ts +146 -0
- package/dist/combine.d.ts.map +1 -0
- package/dist/combine.js +353 -0
- package/dist/combine.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +66 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +134 -0
- package/dist/internal.js.map +1 -0
- package/dist/iterator-tools.d.ts +13 -0
- package/dist/iterator-tools.d.ts.map +1 -1
- package/dist/iterator-tools.js +38 -18
- package/dist/iterator-tools.js.map +1 -1
- package/dist/itertools.d.ts.map +1 -1
- package/dist/itertools.js +42 -24
- package/dist/itertools.js.map +1 -1
- package/dist/tee.d.ts +24 -2
- package/dist/tee.d.ts.map +1 -1
- package/dist/tee.js +175 -2
- package/dist/tee.js.map +1 -1
- package/dist/time.d.ts +69 -0
- package/dist/time.d.ts.map +1 -0
- package/dist/time.js +234 -0
- package/dist/time.js.map +1 -0
- package/dist/transduce.d.ts +65 -0
- package/dist/transduce.d.ts.map +1 -1
- package/dist/transduce.js +33 -1
- package/dist/transduce.js.map +1 -1
- package/dist/transducers.d.ts.map +1 -1
- package/dist/transducers.js +86 -16
- package/dist/transducers.js.map +1 -1
- package/package.json +10 -2
- package/readme.md +338 -14
- package/src/async-channel.ts +63 -47
- package/src/combine.ts +441 -0
- package/src/index.ts +2 -0
- package/src/internal.ts +164 -0
- package/src/iterator-tools.ts +48 -20
- package/src/itertools.ts +36 -23
- package/src/tee.ts +202 -2
- package/src/time.ts +262 -0
- package/src/transduce.ts +113 -1
- package/src/transducers.ts +121 -30
package/src/transduce.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { abortable, type SignalOptions } from "./abort.ts";
|
|
2
|
-
import { type ReducerStep, reduceAsync, reduceSync, type Transducer } from "./iterator-tools.ts";
|
|
2
|
+
import { HALT, type ReducerStep, reduceAsync, reduceSync, type Transducer } from "./iterator-tools.ts";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The innermost reducer step ("emit"): pushes an emitted item onto the
|
|
@@ -159,3 +159,115 @@ export function transduceSync(
|
|
|
159
159
|
export function transduceSync(...functions: Array<Transducer<any, any>>) {
|
|
160
160
|
return (itemCollection: Iterable<unknown>) => reduceSync(itemCollection, composeFunctions(...functions)(emit), []);
|
|
161
161
|
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* A push-driven transducer pipeline: feed values in one at a time with
|
|
165
|
+
* `step`, and flush with `complete` when the input ends.
|
|
166
|
+
*/
|
|
167
|
+
export interface PushTransducer<In, Out> {
|
|
168
|
+
/**
|
|
169
|
+
* Run one value through the pipeline and return what it emitted (often
|
|
170
|
+
* zero or one item; `group` and friends may emit several). Returns `[]`
|
|
171
|
+
* once the pipeline has halted or completed.
|
|
172
|
+
*/
|
|
173
|
+
step(value: In): Out[];
|
|
174
|
+
/**
|
|
175
|
+
* Flush stateful transducers (`.complete`, e.g. a trailing partial
|
|
176
|
+
* `group`) and return what they emitted. Call it once when the input
|
|
177
|
+
* ends, or after `halted` becomes true. Later calls return `[]`.
|
|
178
|
+
*/
|
|
179
|
+
complete(): Out[];
|
|
180
|
+
/**
|
|
181
|
+
* What the stateful transducers keep between steps, upstream first: a
|
|
182
|
+
* half-full `group`'s items, `partitionBy`'s open run, `accumulate`'s
|
|
183
|
+
* running value, `dedupe`'s last key. A copy: changing it changes nothing.
|
|
184
|
+
*/
|
|
185
|
+
held(): unknown[];
|
|
186
|
+
/**
|
|
187
|
+
* Let go of the held items `predicate` matches, and return how many. For a
|
|
188
|
+
* caller that must stop an operator keeping something (a value removed
|
|
189
|
+
* elsewhere). It never emits and never changes what is later emitted
|
|
190
|
+
* beyond leaving the dropped items out: a `group` still waits for its full
|
|
191
|
+
* size, a `partitionBy` run left empty is closed without being emitted,
|
|
192
|
+
* `accumulate` starts again from its initial value, and `dedupe` forgets
|
|
193
|
+
* its key (so an equal item passes once).
|
|
194
|
+
*/
|
|
195
|
+
drop(predicate: (held: unknown) => boolean): number;
|
|
196
|
+
/** True once a transducer returned HALT (e.g. `take(n)` saw item n + 1). */
|
|
197
|
+
readonly halted: boolean;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Drive transducers by pushing values in, with no iterator: for code that
|
|
202
|
+
* receives values by callback (an event handler, a wire in a dataflow
|
|
203
|
+
* graph). Uses the same left-to-right composition order, `.complete` flush
|
|
204
|
+
* protocol and HALT semantics as transduceSync:
|
|
205
|
+
* `transduceSync(...t)(xs)` yields exactly what pushing each `x` through
|
|
206
|
+
* `step` and then calling `complete()` returns.
|
|
207
|
+
*
|
|
208
|
+
* Like transduceSync, the items emitted by the step that returns HALT are
|
|
209
|
+
* discarded.
|
|
210
|
+
* @kind function
|
|
211
|
+
* @name transducePush
|
|
212
|
+
* @example
|
|
213
|
+
* ```javascript
|
|
214
|
+
* import { transducePush, transducers } from '@johnhenry/iteration';
|
|
215
|
+
* const { map, group } = transducers;
|
|
216
|
+
* const pipe = transducePush(map((x) => x * 10), group(2));
|
|
217
|
+
* pipe.step(1); // []
|
|
218
|
+
* pipe.step(2); // [[10, 20]]
|
|
219
|
+
* pipe.step(3); // []
|
|
220
|
+
* pipe.complete(); // [[30]]
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
export function transducePush<A, B>(t1: Transducer<A, B>): PushTransducer<A, B>;
|
|
224
|
+
export function transducePush<A, B, C>(t1: Transducer<A, B>, t2: Transducer<B, C>): PushTransducer<A, C>;
|
|
225
|
+
export function transducePush<A, B, C, D>(
|
|
226
|
+
t1: Transducer<A, B>,
|
|
227
|
+
t2: Transducer<B, C>,
|
|
228
|
+
t3: Transducer<C, D>,
|
|
229
|
+
): PushTransducer<A, D>;
|
|
230
|
+
export function transducePush<A, B, C, D, E>(
|
|
231
|
+
t1: Transducer<A, B>,
|
|
232
|
+
t2: Transducer<B, C>,
|
|
233
|
+
t3: Transducer<C, D>,
|
|
234
|
+
t4: Transducer<D, E>,
|
|
235
|
+
): PushTransducer<A, E>;
|
|
236
|
+
export function transducePush<A, B, C, D, E, F>(
|
|
237
|
+
t1: Transducer<A, B>,
|
|
238
|
+
t2: Transducer<B, C>,
|
|
239
|
+
t3: Transducer<C, D>,
|
|
240
|
+
t4: Transducer<D, E>,
|
|
241
|
+
t5: Transducer<E, F>,
|
|
242
|
+
): PushTransducer<A, F>;
|
|
243
|
+
export function transducePush(...functions: Array<Transducer<any, any>>): PushTransducer<unknown, unknown>;
|
|
244
|
+
export function transducePush(...functions: Array<Transducer<any, any>>): PushTransducer<unknown, unknown> {
|
|
245
|
+
const step = composeFunctions(...functions)(emit);
|
|
246
|
+
let halted = false;
|
|
247
|
+
let completed = false;
|
|
248
|
+
return {
|
|
249
|
+
step(value) {
|
|
250
|
+
if (halted || completed) return [];
|
|
251
|
+
const out = step([], value);
|
|
252
|
+
if (out === HALT) {
|
|
253
|
+
halted = true;
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
return out;
|
|
257
|
+
},
|
|
258
|
+
complete() {
|
|
259
|
+
if (completed) return [];
|
|
260
|
+
completed = true;
|
|
261
|
+
return step.complete ? step.complete([]) : [];
|
|
262
|
+
},
|
|
263
|
+
held() {
|
|
264
|
+
return step.held ? step.held() : [];
|
|
265
|
+
},
|
|
266
|
+
drop(predicate) {
|
|
267
|
+
return step.drop ? step.drop(predicate) : 0;
|
|
268
|
+
},
|
|
269
|
+
get halted() {
|
|
270
|
+
return halted;
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
}
|
package/src/transducers.ts
CHANGED
|
@@ -8,6 +8,56 @@
|
|
|
8
8
|
|
|
9
9
|
import { HALT, type ReducerStep, type Transducer } from "./iterator-tools.ts";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Give a stateless transducer's step the inner step's `.complete`, so the
|
|
13
|
+
* end-of-source flush cascades through it to a stateful transducer further
|
|
14
|
+
* down the pipeline (e.g. the trailing partial group in
|
|
15
|
+
* `transduceSync(map(f), group(2))`). A stateless step has nothing of its
|
|
16
|
+
* own to flush, so it forwards the inner `.complete` unchanged. Without
|
|
17
|
+
* this, `reduceSync`/`reduceAsync`/`transducePush` only ever saw the
|
|
18
|
+
* outermost step, which had no `.complete`, and the flush was lost.
|
|
19
|
+
* @ignore
|
|
20
|
+
*/
|
|
21
|
+
const forwardComplete = <In>(
|
|
22
|
+
next: Pick<ReducerStep<never>, "complete" | "held" | "drop">,
|
|
23
|
+
step: ReducerStep<In>,
|
|
24
|
+
): ReducerStep<In> => {
|
|
25
|
+
if (next.complete) step.complete = next.complete;
|
|
26
|
+
if (next.held) step.held = next.held;
|
|
27
|
+
if (next.drop) step.drop = next.drop;
|
|
28
|
+
return step;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Give a stateful step `held`/`drop` over its own state, then the inner
|
|
33
|
+
* step's, so the hooks cascade the way `.complete` does. `own` lists the
|
|
34
|
+
* step's held items; `ownDrop` lets go of the ones `predicate` matches and
|
|
35
|
+
* returns how many.
|
|
36
|
+
* @ignore
|
|
37
|
+
*/
|
|
38
|
+
const holding = <In>(
|
|
39
|
+
next: Pick<ReducerStep<never>, "held" | "drop">,
|
|
40
|
+
step: ReducerStep<In>,
|
|
41
|
+
own: () => unknown[],
|
|
42
|
+
ownDrop: (predicate: (held: unknown) => boolean) => number,
|
|
43
|
+
): ReducerStep<In> => {
|
|
44
|
+
step.held = () => [...own(), ...(next.held ? next.held() : [])];
|
|
45
|
+
step.drop = (predicate) => ownDrop(predicate) + (next.drop ? next.drop(predicate) : 0);
|
|
46
|
+
return step;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** Remove, in place, the items of `list` that `predicate` matches; returns how many. */
|
|
50
|
+
const dropFrom = (list: unknown[], predicate: (held: unknown) => boolean): number => {
|
|
51
|
+
let kept = 0;
|
|
52
|
+
let dropped = 0;
|
|
53
|
+
for (const item of list) {
|
|
54
|
+
if (predicate(item)) dropped++;
|
|
55
|
+
else list[kept++] = item;
|
|
56
|
+
}
|
|
57
|
+
list.length = kept;
|
|
58
|
+
return dropped;
|
|
59
|
+
};
|
|
60
|
+
|
|
11
61
|
/**
|
|
12
62
|
* Create a transducer that maps values
|
|
13
63
|
* @kind function
|
|
@@ -18,8 +68,7 @@ import { HALT, type ReducerStep, type Transducer } from "./iterator-tools.ts";
|
|
|
18
68
|
export const map =
|
|
19
69
|
<In, Out>(transform: (item: In) => Out): Transducer<In, Out> =>
|
|
20
70
|
(conjoin) =>
|
|
21
|
-
|
|
22
|
-
conjoin(init, transform(item));
|
|
71
|
+
forwardComplete(conjoin, (init, item) => conjoin(init, transform(item)));
|
|
23
72
|
|
|
24
73
|
/**
|
|
25
74
|
* Create a transducer that filters values
|
|
@@ -31,8 +80,7 @@ export const map =
|
|
|
31
80
|
export const filter =
|
|
32
81
|
<In>(predicate: (item: In) => boolean): Transducer<In, In> =>
|
|
33
82
|
(conjoin) =>
|
|
34
|
-
|
|
35
|
-
predicate(item) ? conjoin(init, item) : init;
|
|
83
|
+
forwardComplete(conjoin, (init, item) => (predicate(item) ? conjoin(init, item) : init));
|
|
36
84
|
|
|
37
85
|
/**
|
|
38
86
|
* Create a transducer that halts after a given number of values
|
|
@@ -46,7 +94,7 @@ export const take =
|
|
|
46
94
|
(conjoin) => {
|
|
47
95
|
let amount = 0;
|
|
48
96
|
// biome-ignore lint/complexity/noCommaOperator: deliberate single-expression step function
|
|
49
|
-
return (init, item) => (amount < limit ? (amount++, conjoin(init, item)) : HALT);
|
|
97
|
+
return forwardComplete(conjoin, (init, item) => (amount < limit ? (amount++, conjoin(init, item)) : HALT));
|
|
50
98
|
};
|
|
51
99
|
|
|
52
100
|
/**
|
|
@@ -62,8 +110,11 @@ export const drop =
|
|
|
62
110
|
<In>(limit: number): Transducer<In, In> =>
|
|
63
111
|
(conjoin) => {
|
|
64
112
|
let amount = 0;
|
|
65
|
-
|
|
66
|
-
|
|
113
|
+
return forwardComplete(conjoin, (init, item) => {
|
|
114
|
+
const pass = amount >= limit;
|
|
115
|
+
amount++;
|
|
116
|
+
return pass ? conjoin(init, item) : init;
|
|
117
|
+
});
|
|
67
118
|
};
|
|
68
119
|
|
|
69
120
|
/**
|
|
@@ -79,8 +130,7 @@ export const drop =
|
|
|
79
130
|
export const reject =
|
|
80
131
|
<In>(predicate: (item: In) => boolean): Transducer<In, In> =>
|
|
81
132
|
(conjoin) =>
|
|
82
|
-
|
|
83
|
-
predicate(item) ? init : conjoin(init, item);
|
|
133
|
+
forwardComplete(conjoin, (init, item) => (predicate(item) ? init : conjoin(init, item)));
|
|
84
134
|
|
|
85
135
|
/**
|
|
86
136
|
* Create a transducer that groups items by quantity before emitting.
|
|
@@ -109,7 +159,12 @@ export const group =
|
|
|
109
159
|
const buffer = out === HALT ? init : out;
|
|
110
160
|
return conjoin.complete ? conjoin.complete(buffer) : buffer;
|
|
111
161
|
};
|
|
112
|
-
return
|
|
162
|
+
return holding(
|
|
163
|
+
conjoin,
|
|
164
|
+
step,
|
|
165
|
+
() => [...partition],
|
|
166
|
+
(predicate) => dropFrom(partition, predicate),
|
|
167
|
+
);
|
|
113
168
|
};
|
|
114
169
|
|
|
115
170
|
/**
|
|
@@ -134,10 +189,22 @@ export const accumulate =
|
|
|
134
189
|
// directly here would carry accumulated state over into the *next*
|
|
135
190
|
// transduce run that reuses this same accumulate(...) instance.
|
|
136
191
|
let accumulated = initial;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
192
|
+
let has = false;
|
|
193
|
+
return holding(
|
|
194
|
+
conjoin,
|
|
195
|
+
forwardComplete(conjoin, (init, item) => {
|
|
196
|
+
accumulated = func(accumulated, item);
|
|
197
|
+
has = true;
|
|
198
|
+
return conjoin(init, accumulated);
|
|
199
|
+
}),
|
|
200
|
+
() => (has ? [accumulated] : []),
|
|
201
|
+
(predicate) => {
|
|
202
|
+
if (!has || !predicate(accumulated)) return 0;
|
|
203
|
+
accumulated = initial;
|
|
204
|
+
has = false;
|
|
205
|
+
return 1;
|
|
206
|
+
},
|
|
207
|
+
);
|
|
141
208
|
};
|
|
142
209
|
|
|
143
210
|
/**
|
|
@@ -156,15 +223,25 @@ export const dedupe =
|
|
|
156
223
|
(conjoin) => {
|
|
157
224
|
let hasLast = false;
|
|
158
225
|
let lastKey: unknown;
|
|
159
|
-
return (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
226
|
+
return holding(
|
|
227
|
+
conjoin,
|
|
228
|
+
forwardComplete(conjoin, (init, item) => {
|
|
229
|
+
const key = keyFn(item);
|
|
230
|
+
if (hasLast && Object.is(key, lastKey)) {
|
|
231
|
+
return init;
|
|
232
|
+
}
|
|
233
|
+
hasLast = true;
|
|
234
|
+
lastKey = key;
|
|
235
|
+
return conjoin(init, item);
|
|
236
|
+
}),
|
|
237
|
+
() => (hasLast ? [lastKey] : []),
|
|
238
|
+
(predicate) => {
|
|
239
|
+
if (!hasLast || !predicate(lastKey)) return 0;
|
|
240
|
+
hasLast = false;
|
|
241
|
+
lastKey = undefined;
|
|
242
|
+
return 1;
|
|
243
|
+
},
|
|
244
|
+
);
|
|
168
245
|
};
|
|
169
246
|
|
|
170
247
|
/**
|
|
@@ -179,7 +256,7 @@ export const interpose =
|
|
|
179
256
|
<In, Sep = In>(separator: Sep): Transducer<In, In | Sep> =>
|
|
180
257
|
(conjoin) => {
|
|
181
258
|
let started = false;
|
|
182
|
-
return (init, item) => {
|
|
259
|
+
return forwardComplete(conjoin, (init, item) => {
|
|
183
260
|
if (started) {
|
|
184
261
|
const out = conjoin(init, separator);
|
|
185
262
|
if (out === HALT) {
|
|
@@ -189,7 +266,7 @@ export const interpose =
|
|
|
189
266
|
}
|
|
190
267
|
started = true;
|
|
191
268
|
return conjoin(init, item);
|
|
192
|
-
};
|
|
269
|
+
});
|
|
193
270
|
};
|
|
194
271
|
|
|
195
272
|
/**
|
|
@@ -235,7 +312,21 @@ export const partitionBy =
|
|
|
235
312
|
const buffer = out === HALT ? init : out;
|
|
236
313
|
return conjoin.complete ? conjoin.complete(buffer) : buffer;
|
|
237
314
|
};
|
|
238
|
-
return
|
|
315
|
+
return holding(
|
|
316
|
+
conjoin,
|
|
317
|
+
step,
|
|
318
|
+
() => (hasCurrent ? [...bucket] : []),
|
|
319
|
+
(predicate) => {
|
|
320
|
+
const dropped = dropFrom(bucket, predicate);
|
|
321
|
+
if (hasCurrent && bucket.length === 0) {
|
|
322
|
+
// A run with nothing left isn't open: the next item starts a fresh
|
|
323
|
+
// one, and complete() has no empty run to flush.
|
|
324
|
+
hasCurrent = false;
|
|
325
|
+
currentKey = undefined;
|
|
326
|
+
}
|
|
327
|
+
return dropped;
|
|
328
|
+
},
|
|
329
|
+
);
|
|
239
330
|
};
|
|
240
331
|
|
|
241
332
|
/**
|
|
@@ -250,7 +341,7 @@ export const partitionBy =
|
|
|
250
341
|
export const tap =
|
|
251
342
|
<In>(fn: (item: In) => unknown): Transducer<In, In> =>
|
|
252
343
|
(conjoin) =>
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
344
|
+
forwardComplete(conjoin, (init, item) => {
|
|
345
|
+
fn(item);
|
|
346
|
+
return conjoin(init, item);
|
|
347
|
+
});
|