@aidc-toolkit/utility 0.9.0 → 0.9.1

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/src/iteration.ts DELETED
@@ -1,343 +0,0 @@
1
- /**
2
- * Iteration source type. The underlying source is an iterable or iterator or a callback to an iterable or iterator.
3
- */
4
- export type IterationSource<T> = Iterable<T> | Iterator<T> | (() => (Iterable<T> | Iterator<T>));
5
-
6
- /**
7
- * Iteration proxy class for applying a callback for mapping or filtering.
8
- */
9
- class IterationProxy<T, U, V> implements IterableIterator<V> {
10
- /**
11
- * Proxied iterable iterator.
12
- */
13
- private readonly _proxiedIterableIterator: IterableIterator<T>;
14
-
15
- /**
16
- * Callback for map or filter.
17
- */
18
- private readonly _callback: (element: T, index: number) => U;
19
-
20
- /**
21
- * If true, callback is a predicate for a filter.
22
- */
23
- private readonly _isPredicate: boolean;
24
-
25
- /**
26
- * Index into proxied iterable iterator.
27
- */
28
- private _index: number;
29
-
30
- /**
31
- * Constructor.
32
- *
33
- * @param proxiedIterableIterator
34
- * Proxied iterable iterator.
35
- *
36
- * @param callback
37
- * Callback for map or filter.
38
- *
39
- * @param isPredicate
40
- * If true, callback is a predicate for a filter.
41
- */
42
- constructor(proxiedIterableIterator: IterableIterator<T>, callback: (element: T, index: number) => U, isPredicate: boolean) {
43
- this._proxiedIterableIterator = proxiedIterableIterator;
44
- this._callback = callback;
45
- this._isPredicate = isPredicate;
46
-
47
- this._index = 0;
48
- }
49
-
50
- /**
51
- * {@link Iterable} interface implementation.
52
- *
53
- * @returns
54
- * Iterable iterator.
55
- */
56
- [Symbol.iterator](): IterableIterator<V> {
57
- return this;
58
- }
59
-
60
- /**
61
- * {@link Iterator} interface implementation.
62
- *
63
- * @param args
64
- * Arguments.
65
- *
66
- * @returns
67
- * Next element or number or total number of elements if none.
68
- */
69
- next(...args: [] | [undefined]): IteratorResult<V, number> {
70
- let done = false;
71
- let value: V | undefined;
72
-
73
- let callbackDone: boolean;
74
-
75
- do {
76
- const proxiedNext = this._proxiedIterableIterator.next(...args);
77
-
78
- if (!(proxiedNext.done ?? false)) {
79
- const proxiedValue = proxiedNext.value;
80
- const callbackValue = this._callback(proxiedValue, this._index++);
81
-
82
- if (!this._isPredicate) {
83
- // Types U and V are known to be identical.
84
- value = callbackValue as unknown as V;
85
-
86
- callbackDone = true;
87
- } else {
88
- callbackDone = callbackValue as boolean;
89
-
90
- if (callbackDone) {
91
- // Types T and V are known to be identical.
92
- value = proxiedValue as unknown as V;
93
- }
94
- }
95
- } else {
96
- done = true;
97
- callbackDone = true;
98
- }
99
- } while (!callbackDone);
100
-
101
- return done ?
102
- {
103
- done: true,
104
- value: this._index
105
- } :
106
- {
107
- done: false,
108
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
109
- value: value!
110
- };
111
- }
112
- }
113
-
114
- /**
115
- * Iteration helper. Adds array-like functionality through {@link forEach}, {@link map}, {@link filter}, and
116
- * {@link reduce} methods. Likely to be refactored as {@link
117
- * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator#iterator_helpers | iterator
118
- * helpers} are more widely deployed.
119
- */
120
- export class IterationHelper<T> implements IterableIterator<T> {
121
- /**
122
- * Iteration source.
123
- */
124
- private readonly _iterationSource: IterationSource<T>;
125
-
126
- /**
127
- * Iterable extracted from iteration source.
128
- */
129
- private _iterable?: Iterable<T>;
130
-
131
- /**
132
- * Iterator extracted from iteration source.
133
- */
134
- private _iterator?: Iterator<T>;
135
-
136
- /**
137
- * Constructor.
138
- *
139
- * @param iterationSource
140
- * Iteration source.
141
- */
142
- private constructor(iterationSource: IterationSource<T>) {
143
- this._iterationSource = iterationSource;
144
- }
145
-
146
- /**
147
- * Get an iteration helper from an iteration source. If the iteration source is itself an iteration helper, it is
148
- * returned verbatim, otherwise a new iteration helper is constructed.
149
- *
150
- * @param iterationSource
151
- * Iteration source.
152
- *
153
- * @returns
154
- * Iteration helper.
155
- */
156
- static from<T>(iterationSource: IterationSource<T>): IterationHelper<T> {
157
- return iterationSource instanceof IterationHelper ? iterationSource as IterationHelper<T> : new IterationHelper(iterationSource);
158
- }
159
-
160
- /**
161
- * Get the iteration source.
162
- */
163
- get iterationSource(): IterationSource<T> {
164
- return this._iterationSource;
165
- }
166
-
167
- /**
168
- * Get the iteration source as an iterable.
169
- *
170
- * @returns
171
- * Iterable.
172
- */
173
- asIterable(): Iterable<T> {
174
- if (this._iterable === undefined) {
175
- const resolvedIterationSource = typeof this.iterationSource === "function" ? this.iterationSource() : this.iterationSource;
176
-
177
- this._iterable = Symbol.iterator in resolvedIterationSource ?
178
- resolvedIterationSource :
179
- {
180
- [Symbol.iterator](): Iterator<T> {
181
- return resolvedIterationSource;
182
- }
183
- };
184
- }
185
-
186
- return this._iterable;
187
- }
188
-
189
- /**
190
- * Get the iteration source as an array.
191
- *
192
- * @returns
193
- * Array.
194
- */
195
- asArray(): readonly T[] {
196
- const iterable = this.asIterable();
197
-
198
- // Return iterable as array.
199
- return Array.isArray(iterable) ? iterable as readonly T[] : Array.from(iterable);
200
- }
201
-
202
- /**
203
- * Get the iteration source as an iterator.
204
- *
205
- * @returns
206
- * Iterator.
207
- */
208
- asIterator(): Iterator<T> {
209
- if (this._iterator === undefined) {
210
- this._iterator = this.asIterable()[Symbol.iterator]();
211
- }
212
-
213
- return this._iterator;
214
- }
215
-
216
- /**
217
- * Get the iteration source as a callback.
218
- *
219
- * @returns
220
- * Callback.
221
- */
222
- asCallback(): () => IterationSource<T> {
223
- return typeof this._iterationSource === "function" ? this._iterationSource : () => this._iterationSource;
224
- }
225
-
226
- /**
227
- * {@link Iterable} interface implementation.
228
- *
229
- * @returns
230
- * Iterable iterator.
231
- */
232
- [Symbol.iterator](): IterableIterator<T> {
233
- return this;
234
- }
235
-
236
- /**
237
- * {@link Iterator} interface implementation.
238
- *
239
- * @param args
240
- * Arguments.
241
- *
242
- * @returns
243
- * Next element.
244
- */
245
- next(...args: [] | [undefined]): IteratorResult<T, unknown> {
246
- return this.asIterator().next(...args);
247
- }
248
-
249
- /**
250
- * Perform an action for each element in the iteration helper.
251
- *
252
- * @param callback
253
- * Callback that processes the element and its index in the iteration sequence.
254
- */
255
- forEach(callback: (element: T, index: number) => void): void {
256
- let index = 0;
257
-
258
- for (const element of this) {
259
- callback(element, index++);
260
- }
261
- }
262
-
263
- /**
264
- * Map the iteration helper to another iteration helper by performing an action for each element in the iteration
265
- * helper along the way.
266
- *
267
- * @param callback
268
- * Callback that processes the element and its index in the iteration sequence.
269
- *
270
- * @returns
271
- * Iterable iterator over callback results.
272
- */
273
- map<U>(callback: (element: T, index: number) => U): IterableIterator<U> {
274
- return new IterationProxy(this, callback, false);
275
- }
276
-
277
- /**
278
- * Filter the iteration helper based on the condition specified in a predicate. Each call to `.next()` will iterate
279
- * as far as necessary until it reaches an element that satisfies the predicate. Care should be taken when working
280
- * with large iterators and infrequently truthy predicates.
281
- *
282
- * @param predicate
283
- * Predicate that processes the element and its index in the iteration sequence.
284
- *
285
- * @returns
286
- * Iterator iterable over elements that satisfy the predicate.
287
- */
288
- filter(predicate: (element: T, index: number) => boolean): IterableIterator<T> {
289
- return new IterationProxy(this, predicate, true);
290
- }
291
-
292
- /**
293
- * Reduce the iterator to a single value by applying a callback.
294
- *
295
- * @param callback
296
- * Callback that processes the previous return value of the callback, the current value of the iterator, and the
297
- * current index. The initial value is considered to be the first element in the iteration helper.
298
- *
299
- * @returns
300
- * Reduced value.
301
- */
302
- reduce(callback: (previousValue: T, currentValue: T, currentIndex: number) => T): T;
303
-
304
- /**
305
- * Reduce the iterator to a single value by applying a callback.
306
- *
307
- * @param callback
308
- * Callback that processes the previous return value of the callback, the current value of the iterator, and the
309
- * current index.
310
- *
311
- * @param initialValue
312
- * Initial value, passed as the first previous return value of the callback.
313
- *
314
- * @returns
315
- * Reduced value.
316
- */
317
- reduce(callback: (previousValue: T, currentValue: T, currentIndex: number) => T, initialValue: T): T;
318
-
319
- reduce<U>(callback: (previousValue: U, currentValue: T, currentIndex: number) => U, initialValue?: U): U {
320
- let index = 0;
321
- let result = initialValue;
322
-
323
- for (const value of this) {
324
- if (index === 0 && initialValue === undefined) {
325
- result = value as unknown as U;
326
- } else {
327
- // Iteration has occurred at least once so result is of the expected type.
328
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
329
- result = callback(result!, value, index);
330
- }
331
-
332
- index++;
333
- }
334
-
335
- if (index === 0 && initialValue === undefined) {
336
- throw new Error("reduce() of empty iterator with no initial value");
337
- }
338
-
339
- // Iteration has occurred at least once so result is of the expected type.
340
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
341
- return result!;
342
- }
343
- }
@@ -1,282 +0,0 @@
1
- import { describe, expect, test } from "vitest";
2
- import { IterationHelper, type IterationSource } from "../src/index.js";
3
-
4
- const source: readonly string[] = [
5
- "1", "2", "3", "4", "5"
6
- ];
7
-
8
- // eslint-disable-next-line jsdoc/require-jsdoc
9
- function iterableSource(): Iterable<string> {
10
- return [...source];
11
- }
12
-
13
- // eslint-disable-next-line jsdoc/require-jsdoc
14
- function arraySource(): string[] {
15
- return [...source];
16
- }
17
-
18
- // eslint-disable-next-line jsdoc/require-jsdoc
19
- function iteratorSource(): Iterator<string> {
20
- return [...source][Symbol.iterator]();
21
- }
22
-
23
- // eslint-disable-next-line jsdoc/require-jsdoc
24
- function * generatorSource(): Generator<string> {
25
- for (const s of source) {
26
- yield s;
27
- }
28
- }
29
-
30
- // eslint-disable-next-line jsdoc/require-jsdoc
31
- function callbackSource(): string[] {
32
- return [...source];
33
- }
34
-
35
- describe("Iterable", () => {
36
- // eslint-disable-next-line jsdoc/require-jsdoc
37
- function validateIterable(iterationSource: IterationSource<string>, testEquality: boolean): void {
38
- const iterationHelper = IterationHelper.from(iterationSource);
39
-
40
- expect(IterationHelper.from(iterationHelper)).toBe(iterationHelper);
41
- expect(iterationHelper.iterationSource).toBe(iterationSource);
42
-
43
- expect(iterationHelper.asIterable() === iterationSource).toBe(testEquality);
44
- expect(Array.from(iterationHelper)).toStrictEqual(source);
45
- }
46
-
47
- test("Iterable", () => {
48
- validateIterable(iterableSource, false);
49
- validateIterable(iterableSource(), true);
50
- });
51
-
52
- test("Array", () => {
53
- validateIterable(arraySource, false);
54
- validateIterable(arraySource(), true);
55
- });
56
-
57
- test("Iterator", () => {
58
- validateIterable(iteratorSource, false);
59
- validateIterable(iteratorSource(), true);
60
- });
61
-
62
- test("Generator", () => {
63
- validateIterable(generatorSource, false);
64
- validateIterable(generatorSource(), true);
65
- });
66
-
67
- test("Callback", () => {
68
- validateIterable(callbackSource, false);
69
- validateIterable(callbackSource(), true);
70
- });
71
- });
72
-
73
- describe("Array", () => {
74
- // eslint-disable-next-line jsdoc/require-jsdoc
75
- function validateArray(iterationSource: IterationSource<string>, testEquality: boolean): void {
76
- const iterationHelper = IterationHelper.from(iterationSource);
77
-
78
- expect(IterationHelper.from(iterationHelper)).toBe(iterationHelper);
79
- expect(iterationHelper.iterationSource).toBe(iterationSource);
80
-
81
- const array = iterationHelper.asArray();
82
-
83
- expect(array === iterationSource).toBe(testEquality);
84
- expect(array).toStrictEqual(source);
85
- }
86
-
87
- test("Iterable", () => {
88
- validateArray(iterableSource, false);
89
- validateArray(iterableSource(), true);
90
- });
91
-
92
- test("Array", () => {
93
- validateArray(arraySource, false);
94
- validateArray(arraySource(), true);
95
- });
96
-
97
- test("Iterator", () => {
98
- validateArray(iteratorSource, false);
99
- validateArray(iteratorSource(), false);
100
- });
101
-
102
- test("Generator", () => {
103
- validateArray(generatorSource, false);
104
- validateArray(generatorSource(), false);
105
- });
106
-
107
- test("Callback", () => {
108
- validateArray(callbackSource, false);
109
- validateArray(callbackSource(), true);
110
- });
111
- });
112
-
113
- describe("Iterator", () => {
114
- // eslint-disable-next-line jsdoc/require-jsdoc
115
- function validateIterator(iterationSource: IterationSource<string>, testEquality: boolean): void {
116
- const iterationHelper = IterationHelper.from(iterationSource);
117
-
118
- expect(IterationHelper.from(iterationHelper)).toBe(iterationHelper);
119
- expect(iterationHelper.iterationSource).toBe(iterationSource);
120
-
121
- const iterator = iterationHelper.asIterator();
122
-
123
- expect(iterator === iterationSource).toBe(testEquality);
124
- expect(Array.from({
125
- [Symbol.iterator](): Iterator<string> {
126
- return iterator;
127
- }
128
- })).toStrictEqual(source);
129
- }
130
-
131
- test("Iterable", () => {
132
- validateIterator(iterableSource, false);
133
- validateIterator(iterableSource(), false);
134
- });
135
-
136
- test("Array", () => {
137
- validateIterator(arraySource, false);
138
- validateIterator(arraySource(), false);
139
- });
140
-
141
- test("Iterator", () => {
142
- validateIterator(iteratorSource, false);
143
- validateIterator(iteratorSource(), true);
144
- });
145
-
146
- test("Generator", () => {
147
- validateIterator(generatorSource, false);
148
- validateIterator(generatorSource(), true);
149
- });
150
-
151
- test("Callback", () => {
152
- validateIterator(callbackSource, false);
153
- validateIterator(callbackSource(), false);
154
- });
155
- });
156
-
157
- describe("Callback", () => {
158
- // eslint-disable-next-line jsdoc/require-jsdoc
159
- function validateCallback(iterationSource: IterationSource<string>, testEquality: boolean): void {
160
- const iterationHelper = IterationHelper.from(iterationSource);
161
-
162
- expect(IterationHelper.from(iterationHelper)).toBe(iterationHelper);
163
- expect(iterationHelper.iterationSource).toBe(iterationSource);
164
-
165
- expect(iterationHelper.asCallback() === iterationSource).toBe(testEquality);
166
- expect(iterationHelper.asArray()).toStrictEqual(source);
167
- }
168
-
169
- test("Iterable", () => {
170
- validateCallback(iterableSource, true);
171
- validateCallback(iterableSource(), false);
172
- });
173
-
174
- test("Array", () => {
175
- validateCallback(arraySource, true);
176
- validateCallback(arraySource(), false);
177
- });
178
-
179
- test("Iterator", () => {
180
- validateCallback(iteratorSource, true);
181
- validateCallback(iteratorSource(), false);
182
- });
183
-
184
- test("Generator", () => {
185
- validateCallback(generatorSource, true);
186
- validateCallback(generatorSource(), false);
187
- });
188
-
189
- test("Callback", () => {
190
- validateCallback(callbackSource, true);
191
- validateCallback(callbackSource(), false);
192
- });
193
- });
194
-
195
- describe("Helpers", () => {
196
- test("For each", () => {
197
- let count = 0;
198
-
199
- IterationHelper.from(source).forEach((value, index) => {
200
- expect(Number(value)).toBe(index + 1);
201
- expect(index).toBe(count++);
202
- });
203
-
204
- expect(count).toBe(source.length);
205
- });
206
-
207
- test("Map", () => {
208
- let count = 0;
209
-
210
- const mappedIterationHelper = IterationHelper.from(source).map((element, index) => {
211
- expect(Number(element)).toBe(index + 1);
212
- expect(index).toBe(count++);
213
-
214
- return -count;
215
- });
216
-
217
- expect(count).toBe(0);
218
-
219
- let negativeCount = 0;
220
-
221
- for (const element of mappedIterationHelper) {
222
- expect(element).toBe(--negativeCount);
223
- }
224
-
225
- expect(count).toBe(source.length);
226
- });
227
-
228
- test("Filter", () => {
229
- let count = 0;
230
-
231
- const filteredIterable = IterationHelper.from(source).filter((element, index) => {
232
- expect(Number(element)).toBe(index + 1);
233
- expect(index).toBe(count++);
234
-
235
- return Number(element) % 2 === 0;
236
- });
237
-
238
- expect(count).toBe(0);
239
-
240
- let evenCount = 0;
241
-
242
- for (const element of filteredIterable) {
243
- const n = Number(element);
244
-
245
- expect(n % 2).toBe(0);
246
- expect(Math.floor((n - 1) / 2)).toBe(evenCount++);
247
- }
248
-
249
- expect(count).toBe(source.length);
250
- expect(evenCount).toBe(Math.floor(source.length / 2));
251
- });
252
-
253
- test("Reduce no initial value", () => {
254
- let count = 0;
255
-
256
- expect(IterationHelper.from(source).reduce((previousValue, currentValue, currentIndex) => {
257
- expect(Number(currentValue)).toBe(currentIndex + 1);
258
- expect(currentIndex - 1).toBe(count++);
259
-
260
- return previousValue + currentValue;
261
- })).toBe("".concat(...source));
262
-
263
- expect(count).toBe(source.length - 1);
264
-
265
- expect(() => IterationHelper.from<string>([]).reduce(() => "")).toThrow("reduce() of empty iterator with no initial value");
266
- });
267
-
268
- test("Reduce initial value", () => {
269
- let count = 0;
270
-
271
- expect(IterationHelper.from(source).reduce((previousValue, currentValue, currentIndex) => {
272
- expect(Number(currentValue)).toBe(currentIndex + 1);
273
- expect(currentIndex).toBe(count++);
274
-
275
- return previousValue + currentValue;
276
- }, "0")).toBe("0".concat(...source));
277
-
278
- expect(count).toBe(source.length);
279
-
280
- expect(IterationHelper.from<string>([]).reduce(() => "", "0")).toBe("0");
281
- });
282
- });