extra-iterator 0.4.0 → 0.4.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.
File without changes
@@ -0,0 +1 @@
1
+ "use strict";
package/dist/index.d.ts CHANGED
@@ -12,12 +12,36 @@ export declare class ExtraIterator<T> extends Iterator<T, any, any> {
12
12
  static zip<A, B, C, D, E, F>(a: ExtraIteratorSource<A>, b: ExtraIteratorSource<B>, c: ExtraIteratorSource<C>, d: ExtraIteratorSource<D>, e: ExtraIteratorSource<E>, f: ExtraIteratorSource<F>): ExtraIterator<[A, B, C, D, E, F]>;
13
13
  static zip<A, B, C, D, E, F, G>(a: ExtraIteratorSource<A>, b: ExtraIteratorSource<B>, c: ExtraIteratorSource<C>, d: ExtraIteratorSource<D>, e: ExtraIteratorSource<E>, f: ExtraIteratorSource<F>, g: ExtraIteratorSource<G>): ExtraIterator<[A, B, C, D, E, F, G]>;
14
14
  static zip<A, B, C, D, E, F, G, H>(a: ExtraIteratorSource<A>, b: ExtraIteratorSource<B>, c: ExtraIteratorSource<C>, d: ExtraIteratorSource<D>, e: ExtraIteratorSource<E>, f: ExtraIteratorSource<F>, g: ExtraIteratorSource<G>, h: ExtraIteratorSource<H>): ExtraIterator<[A, B, C, D, E, F, G, H]>;
15
+ /**
16
+ * Creates an iterator that yields arrays containing the values that exist in the same indexes in each of the
17
+ * provided iterators.
18
+ *
19
+ * @example ExtraIterator.zip([1, 2, 3], ['a', 'b', 'c']).toArray() // returns [ [1, 'a'], [2, 'b'], [3, 'c'] ]
20
+ */
15
21
  static zip<T>(...iterables: ExtraIteratorSource<T>[]): ExtraIterator<T[]>;
22
+ /**
23
+ * Creates an iterator that yields no value.
24
+ *
25
+ * @example ExtraIterator.empty().toArray() // returns []
26
+ */
16
27
  static empty<T = any>(): ExtraIterator<T>;
28
+ /**
29
+ * Creates an iterator that yields incrementing numbers, starting from
30
+ * `start` (default 0) and ending at `end`. (exclusive)
31
+ *
32
+ * If `end` is omitted, counts to Infinity.
33
+ *
34
+ * @example ExtraIterator.count(5).toArray() // returns [0, 1, 2, 3, 4]
35
+ */
17
36
  static count(): ExtraIterator<number>;
18
37
  static count(end: number): ExtraIterator<number>;
19
38
  static count(start: number, end: number): ExtraIterator<number>;
20
39
  static count(start: number, end: number, interval: number): ExtraIterator<number>;
40
+ /**
41
+ * Creates an iterator that yields the provided value `count` times.
42
+ *
43
+ * @example ExtraIterator.repeat(3, 'a').toArray() // returns ['a', 'a', 'a']
44
+ */
21
45
  static repeat<T>(count: number, value: T): ExtraIterator<T>;
22
46
  private constructor();
23
47
  private source;
@@ -29,29 +53,251 @@ export declare class ExtraIterator<T> extends Iterator<T, any, any> {
29
53
  private takeLast;
30
54
  drop(count: number): ExtraIterator<T>;
31
55
  flatMap<U>(callback: (value: T, index: number) => Iterator<U, unknown, undefined> | Iterable<U, unknown, undefined>): ExtraIterator<U>;
56
+ /**
57
+ * Flattens the iterator by one level. If the iterator yields iterables, it will yield their values.
58
+ * If the iterator yields non-iterables, it will yield the values as is.
59
+ *
60
+ * This is equivalent to `flatMap(value => value)`.
61
+ *
62
+ * @example ExtraIterator.from([[1, 2], [3, 4]]).flatten().toArray() // returns [1, 2, 3, 4]
63
+ */
32
64
  flatten(): T extends Iterable<infer U> ? ExtraIterator<U> : never;
33
- groupBy<K extends string | symbol>(callbackfn: (value: T, index: number) => K): Record<K, T[]>;
65
+ /**
66
+ * Creates a new iterator that yields the values of this iterator, but won't yield any duplicates.
67
+ *
68
+ * @param keyProvider An optional function that returns a key for each value. The keys are used to determine whether
69
+ * two values are equal or not. If two values have the same key, only the first one will be yielded. The other is
70
+ * ignored.
71
+ *
72
+ * @example ExtraIteartor.from('determination')
73
+ * .unique()
74
+ * .toArray()
75
+ * // returns ['d', 'e', 't', 'r', 'm', 'i', 'n', 'a', 'o']
76
+ */
34
77
  unique(keyProvider?: (value: T) => unknown): ExtraIterator<T>;
78
+ /**
79
+ * Creates a new iterator that yields the values of this iterator, but won't yield any null or undefined values.
80
+ *
81
+ * @example ExtraIterator.from([0, 1, null, 3, undefined, 5])
82
+ * .compact()
83
+ * .toArray()
84
+ * // returns [0, 1, 3, 5]
85
+ */
35
86
  compact(): ExtraIterator<Exclude<T, null | undefined>>;
36
- withEach(callbackfn: (value: T, index: number) => void): ExtraIterator<T>;
37
- first(): T | undefined;
38
- last(): T | undefined;
39
- at(index: number): T | undefined;
40
- concat(items: Iterable<T>): ExtraIterator<T>;
87
+ /**
88
+ * Appends a new value to the end of the iterator.
89
+ *
90
+ * @example
91
+ *
92
+ * ExtraIterator.from([1, 2, 3])
93
+ * .append(4)
94
+ * .toArray()
95
+ * // returns [1, 2, 3, 4]
96
+ */
41
97
  append(item: T): ExtraIterator<T>;
42
- prependMany(items: Iterable<T>): ExtraIterator<T>;
98
+ /**
99
+ * Prepends a new value to the beginning of the iterator. The new value will be yielded first, then the rest of this
100
+ * iterator will be yielded.
101
+ *
102
+ * @example
103
+ *
104
+ * ExtraIterator.from([1, 2, 3])
105
+ * .prepend(0)
106
+ * .toArray()
107
+ * // returns [0, 1, 2, 3]
108
+ */
43
109
  prepend(item: T): ExtraIterator<T>;
110
+ /**
111
+ * Concatenates multiple values to the end of this iterator.
112
+ *
113
+ * @example
114
+ *
115
+ * ExtraIterator.from([1, 2, 3])
116
+ * .concat([4, 5, 6])
117
+ * .toArray()
118
+ * // returns [1, 2, 3, 4, 5, 6]
119
+ */
120
+ concat(items: Iterable<T>): ExtraIterator<T>;
121
+ /**
122
+ * Concatenates multiple values to the start of this iterator.
123
+ *
124
+ * The order of the values is preserved. This means the first element yielded by the returning iterator will be the
125
+ * first element of the `items` param; and the first element of this iterator will be yielded after the last element
126
+ * of the `items` param.
127
+ *
128
+ * @example
129
+ *
130
+ * ExtraIterator.from([4, 5, 6])
131
+ * .prependMany([1, 2, 3])
132
+ * .toArray()
133
+ * // returns [1, 2, 3, 4, 5, 6]
134
+ */
135
+ prependMany(items: Iterable<T>): ExtraIterator<T>;
136
+ /**
137
+ * Creates a new iterator that invokes the provided callback function over each element of this iterator and yields
138
+ * the elements for which the callback returns `true`, only for as long as the callback returns `true`.
139
+ *
140
+ * This is similar to {@link filter}, except iteration stops once the callback returns `false` for the first time.
141
+ *
142
+ * @example
143
+ *
144
+ * ExtraIterator.from(['Alice', 'Antony', 'Charlie', 'Ashley'])
145
+ * .takeWhile(name => name[0] === 'A')
146
+ * .toArray()
147
+ * // returns ['Alice', 'Antony']
148
+ *
149
+ * // ℹ Note that, in the example above, `filter()` would have returned `['Alice', 'Antony', 'Ashley']` instead.
150
+ */
44
151
  takeWhile(predicate: (value: T, index: number) => boolean): ExtraIterator<T>;
152
+ /**
153
+ * Creates a new iterator that invokes the provided callback function over each element of this iterator and skips
154
+ * the elements for which the callback returns `true`, but only for as long as the callback returns `true`.
155
+ *
156
+ * Once the callback function returns `false`, it will no longer be called. The iterator yields the element that
157
+ * caused the callback to return `false`, and well as the subsequent elements.
158
+ *
159
+ * @example
160
+ *
161
+ * ExtraIterator.from(['Alice', 'Antony', 'Charlie', 'Ashley'])
162
+ * .dropWhile(name => name[0] === 'A')
163
+ * .toArray()
164
+ * // returns ['Charlie', 'Ashley']
165
+ */
45
166
  dropWhile(predicate: (value: T, index: number) => boolean): ExtraIterator<T>;
167
+ /**
168
+ * Groups the elements in this iterator into arrays of fixed size.
169
+ * The last array might be smaller than the others if the number of elements in this iterator is not divisible by
170
+ * the provided `size` param.
171
+ *
172
+ * @example ExtraIterator.from([1, 2, 3, 4, 5, 6, 7])
173
+ * .chunk(3)
174
+ * .toArray()
175
+ * // returns [[1, 2, 3], [4, 5, 6], [7]]
176
+ */
46
177
  chunk(size: number): ExtraIterator<T[]>;
178
+ /**
179
+ * Creates a new iterator that iterates on this iterator and the proviuded other iterator, yielding arrays of pairs
180
+ * of elements from this iterator and the other.
181
+ *
182
+ * The elements in this iterator are the first elements of the pairs, and the elements in the other iterator are the
183
+ * second elements of the pairs.
184
+ *
185
+ * @example ExtraIterator.from([1, 2, 3])
186
+ * .zip(['a', 'b', 'c'])
187
+ * .toArray()
188
+ * // returns [[1, 'a'], [2, 'b'], [3, 'c']]
189
+ */
47
190
  zip<U>(other: Iterable<U>): ExtraIterator<[T, U]>;
191
+ /**
192
+ * Creates a new iterator that yields the values of this iterator interposed by the provided separator. i.e. The
193
+ * separator is inserted between each pair of subsequent elements of this iterator.
194
+ *
195
+ * @example
196
+ *
197
+ * ExtraIterator.from([1, 2, 3, 4])
198
+ * .interpose('a')
199
+ * .toArray()
200
+ * // returns [1, 'a', 2, 'a', 3, 'a', 4]
201
+ */
48
202
  interpose<U>(separator: U): ExtraIterator<T | U>;
203
+ /**
204
+ * Creates a new iterator that yields the values of this iterator and the values of the provided iterator
205
+ * interleaved (alternating). The elements of this iterator always come before the elements of the other iterator.
206
+ *
207
+ * @example
208
+ *
209
+ * ExtraIterator.from([1, 2, 3])
210
+ * .interleave(['a', 'b', 'c'])
211
+ * .toArray()
212
+ * // returns [1, 'a', 2, 'b', 3, 'c']
213
+ */
49
214
  interleave<U>(other: ExtraIteratorSource<U>): ExtraIterator<T | U>;
215
+ /**
216
+ * Replaces some elements of this iterator with new values.
217
+ *
218
+ * @param startIndex The index of the first element to be replaced.
219
+ * @param deleteCount The number of elements to be replaced.
220
+ * @param newItems The new elements to be inserted.
221
+ *
222
+ * @example
223
+ *
224
+ * ExtraIterator.from([1, 2, 3, 4])
225
+ * .splice(1, 2, 5, 6)
226
+ * .toArray()
227
+ * // returns [1, 5, 6, 4]
228
+ */
50
229
  splice(startIndex: number, deleteCount: number, ...newItems: T[]): ExtraIterator<T>;
230
+ /**
231
+ * Returns an iterator the provided element if this iterator is empty; otherwise, it returns this iterator.
232
+ */
51
233
  defaultIfEmpty(provider: () => T): ExtraIterator<T>;
234
+ /**
235
+ * Returns the first element of the iterator, or `undefined` if the iterator is empty.
236
+ */
237
+ first(): T | undefined;
238
+ /**
239
+ * Consumes the iteratror and returns the last value yielded.
240
+ *
241
+ * Returns `undefined` if the iterator is empty.
242
+ */
243
+ last(): T | undefined;
244
+ /**
245
+ * Consumes the iterator and returns the value at the provided index.
246
+ */
247
+ at(index: number): T | undefined;
248
+ /**
249
+ * Groups the elements in this iterator into separate arrays and returns an object containing all the groups.
250
+ *
251
+ * The returned object is composed of keys generated by calling the provided callback function on each element of
252
+ * this iterator, and the value for each key is an array containing all the elements that were assigned to that key.
253
+ *
254
+ * @example
255
+ *
256
+ * ExtraIterator.from([1, 2, 3, 4, 5])
257
+ * .groupBy(value => value % 2 === 0 ? 'even' : 'odd')
258
+ * .toArray()
259
+ * // returns { even: [2, 4], odd: [1, 3, 5] }
260
+ */
261
+ groupBy<K extends string | symbol>(callbackfn: (value: T, index: number) => K): Record<K, T[]>;
262
+ toChainOfResponsibilityFunction<ResultType = void | Promise<void>, ParamsType extends any[] = []>(invokeHandler: (handler: T, next: (...args: ParamsType) => ResultType, ...args: ParamsType) => ResultType): (...args: ParamsType) => ResultType;
263
+ /**
264
+ * Consumes the iterator and returns a value determined by calling the provided function using the iterator as
265
+ * argument.
266
+ *
267
+ * @example
268
+ *
269
+ * ExtraIterator.from(Object.entries({ a: 1, b: 2 }))
270
+ * .map(([key, value]) => ['_' + key, value * 2])
271
+ * .collect(Object.fromEntries)
272
+ * // returns { _a: 2, _b: 4 }
273
+ */
52
274
  collect<U>(collectfn: ((iter: Iterable<T>) => U)): U;
53
275
  toSortedBy(...keys: (keyof T)[]): T[];
276
+ /**
277
+ * Consumes the iterator and returns the number of elements it contained.
278
+ */
54
279
  count(): number;
280
+ /**
281
+ * Consumes the iterator and returns a boolean indicating whether all elements in the iterator were unique.
282
+ *
283
+ * If it returns false, then the iterator had at least one duplicated element.
284
+ *
285
+ * @example
286
+ *
287
+ * ExtraIterator.from([1, 2, 3]).uniqueness() // returns true
288
+ * ExtraIterator.from([1, 2, 3, 1]).uniqueness() // returns false
289
+ */
55
290
  uniqueness(mapper?: (value: T) => unknown): boolean;
291
+ /**
292
+ * Lazily executes a function over each element of this iterator as the values are iterated.
293
+ *
294
+ * This method does not change the output values of the iterator.
295
+ *
296
+ * This method is equivalent to {@link map} when the callback function returns the iterated value.
297
+ *
298
+ * This is similar to {@link forEach}, except the callback function is not executed immediately (instead, it is
299
+ * executed when the iterator is iterated), and this method returns the iterator itself.
300
+ */
301
+ withEach(callbackfn: (value: T, index: number) => void): ExtraIterator<T>;
56
302
  }
57
303
  export {};
package/dist/index.js CHANGED
@@ -1,4 +1,7 @@
1
1
  export class ExtraIterator extends Iterator {
2
+ // =================================================================================================================
3
+ // STATIC FUNCTIONS
4
+ // =================================================================================================================
2
5
  // TODO Consider using a lib like `make-iterator` to transform things into iterators
3
6
  static from(source) {
4
7
  if (!(Symbol.iterator in source) && 'length' in source) {
@@ -18,6 +21,11 @@ export class ExtraIterator extends Iterator {
18
21
  }
19
22
  }().toArray());
20
23
  }
24
+ /**
25
+ * Creates an iterator that yields no value.
26
+ *
27
+ * @example ExtraIterator.empty().toArray() // returns []
28
+ */
21
29
  static empty() {
22
30
  return new ExtraIterator([]);
23
31
  }
@@ -31,6 +39,11 @@ export class ExtraIterator extends Iterator {
31
39
  }
32
40
  }());
33
41
  }
42
+ /**
43
+ * Creates an iterator that yields the provided value `count` times.
44
+ *
45
+ * @example ExtraIterator.repeat(3, 'a').toArray() // returns ['a', 'a', 'a']
46
+ */
34
47
  static repeat(count, value) {
35
48
  return new ExtraIterator(function* () {
36
49
  for (let index = 0; index < count; index++) {
@@ -38,6 +51,9 @@ export class ExtraIterator extends Iterator {
38
51
  }
39
52
  }());
40
53
  }
54
+ // =================================================================================================================
55
+ // PRIVATES
56
+ // =================================================================================================================
41
57
  constructor(source) {
42
58
  super();
43
59
  this.source = Iterator.from(source);
@@ -49,6 +65,9 @@ export class ExtraIterator extends Iterator {
49
65
  }
50
66
  }
51
67
  source;
68
+ // =================================================================================================================
69
+ // OVERRIDES
70
+ // =================================================================================================================
52
71
  next(value) {
53
72
  return this.source.next(value);
54
73
  }
@@ -81,20 +100,34 @@ export class ExtraIterator extends Iterator {
81
100
  flatMap(callback) {
82
101
  return ExtraIterator.from(super.flatMap(callback));
83
102
  }
103
+ // =================================================================================================================
104
+ // TRANSFORMING FUNCTIONS
105
+ // -----------------------------------------------------------------------------------------------------------------
106
+ // These functions transform the iterator somehow and returns a new iterator.
107
+ // =================================================================================================================
108
+ /**
109
+ * Flattens the iterator by one level. If the iterator yields iterables, it will yield their values.
110
+ * If the iterator yields non-iterables, it will yield the values as is.
111
+ *
112
+ * This is equivalent to `flatMap(value => value)`.
113
+ *
114
+ * @example ExtraIterator.from([[1, 2], [3, 4]]).flatten().toArray() // returns [1, 2, 3, 4]
115
+ */
84
116
  flatten() {
85
117
  return this.flatMap(value => value);
86
118
  }
87
- groupBy(callbackfn) {
88
- const result = Object.create(null);
89
- for (let index = 0, next; next = this.next(), !next.done; index++) {
90
- const key = callbackfn(next.value, index);
91
- if (!result[key]) {
92
- result[key] = [];
93
- }
94
- result[key].push(next.value);
95
- }
96
- return result;
97
- }
119
+ /**
120
+ * Creates a new iterator that yields the values of this iterator, but won't yield any duplicates.
121
+ *
122
+ * @param keyProvider An optional function that returns a key for each value. The keys are used to determine whether
123
+ * two values are equal or not. If two values have the same key, only the first one will be yielded. The other is
124
+ * ignored.
125
+ *
126
+ * @example ExtraIteartor.from('determination')
127
+ * .unique()
128
+ * .toArray()
129
+ * // returns ['d', 'e', 't', 'r', 'm', 'i', 'n', 'a', 'o']
130
+ */
98
131
  unique(keyProvider = value => value) {
99
132
  return ExtraIterator.from(function* () {
100
133
  const seen = new Set();
@@ -107,60 +140,102 @@ export class ExtraIterator extends Iterator {
107
140
  }
108
141
  }.call(this));
109
142
  }
143
+ /**
144
+ * Creates a new iterator that yields the values of this iterator, but won't yield any null or undefined values.
145
+ *
146
+ * @example ExtraIterator.from([0, 1, null, 3, undefined, 5])
147
+ * .compact()
148
+ * .toArray()
149
+ * // returns [0, 1, 3, 5]
150
+ */
110
151
  compact() {
111
152
  const predicate = (value => value !== null && value !== undefined);
112
153
  return ExtraIterator.from(this.filter(predicate));
113
154
  }
114
- withEach(callbackfn) {
155
+ /**
156
+ * Appends a new value to the end of the iterator.
157
+ *
158
+ * @example
159
+ *
160
+ * ExtraIterator.from([1, 2, 3])
161
+ * .append(4)
162
+ * .toArray()
163
+ * // returns [1, 2, 3, 4]
164
+ */
165
+ append(item) {
115
166
  return ExtraIterator.from(function* () {
116
- for (let index = 0, next; next = this.next(), !next.done; index++) {
117
- callbackfn(next.value, index);
118
- yield next.value;
119
- }
167
+ yield* this;
168
+ yield item;
120
169
  }.call(this));
121
170
  }
122
- first() {
123
- const next = this.next();
124
- return next.done ? undefined : next.value;
125
- }
126
- last() {
127
- let previousItem = this.next();
128
- if (previousItem.done) {
129
- return undefined;
130
- }
131
- for (let currentItem; currentItem = this.next(), !currentItem.done; previousItem = currentItem)
132
- ;
133
- return previousItem.value;
134
- }
135
- at(index) {
136
- return index === -1 ? this.last()
137
- : index < 0 ? this.take(index).at(0)
138
- : this.drop(index).first();
139
- }
140
- concat(items) {
171
+ /**
172
+ * Prepends a new value to the beginning of the iterator. The new value will be yielded first, then the rest of this
173
+ * iterator will be yielded.
174
+ *
175
+ * @example
176
+ *
177
+ * ExtraIterator.from([1, 2, 3])
178
+ * .prepend(0)
179
+ * .toArray()
180
+ * // returns [0, 1, 2, 3]
181
+ */
182
+ prepend(item) {
141
183
  return ExtraIterator.from(function* () {
184
+ yield item;
142
185
  yield* this;
143
- yield* items;
144
186
  }.call(this));
145
187
  }
146
- append(item) {
188
+ /**
189
+ * Concatenates multiple values to the end of this iterator.
190
+ *
191
+ * @example
192
+ *
193
+ * ExtraIterator.from([1, 2, 3])
194
+ * .concat([4, 5, 6])
195
+ * .toArray()
196
+ * // returns [1, 2, 3, 4, 5, 6]
197
+ */
198
+ concat(items) {
147
199
  return ExtraIterator.from(function* () {
148
200
  yield* this;
149
- yield item;
201
+ yield* items;
150
202
  }.call(this));
151
203
  }
204
+ /**
205
+ * Concatenates multiple values to the start of this iterator.
206
+ *
207
+ * The order of the values is preserved. This means the first element yielded by the returning iterator will be the
208
+ * first element of the `items` param; and the first element of this iterator will be yielded after the last element
209
+ * of the `items` param.
210
+ *
211
+ * @example
212
+ *
213
+ * ExtraIterator.from([4, 5, 6])
214
+ * .prependMany([1, 2, 3])
215
+ * .toArray()
216
+ * // returns [1, 2, 3, 4, 5, 6]
217
+ */
152
218
  prependMany(items) {
153
219
  return ExtraIterator.from(function* () {
154
220
  yield* items;
155
221
  yield* this;
156
222
  }.call(this));
157
223
  }
158
- prepend(item) {
159
- return ExtraIterator.from(function* () {
160
- yield item;
161
- yield* this;
162
- }.call(this));
163
- }
224
+ /**
225
+ * Creates a new iterator that invokes the provided callback function over each element of this iterator and yields
226
+ * the elements for which the callback returns `true`, only for as long as the callback returns `true`.
227
+ *
228
+ * This is similar to {@link filter}, except iteration stops once the callback returns `false` for the first time.
229
+ *
230
+ * @example
231
+ *
232
+ * ExtraIterator.from(['Alice', 'Antony', 'Charlie', 'Ashley'])
233
+ * .takeWhile(name => name[0] === 'A')
234
+ * .toArray()
235
+ * // returns ['Alice', 'Antony']
236
+ *
237
+ * // ℹ Note that, in the example above, `filter()` would have returned `['Alice', 'Antony', 'Ashley']` instead.
238
+ */
164
239
  takeWhile(predicate) {
165
240
  return ExtraIterator.from(function* () {
166
241
  for (let index = 0, next; next = this.next(), !next.done; index++) {
@@ -171,6 +246,20 @@ export class ExtraIterator extends Iterator {
171
246
  }
172
247
  }.call(this));
173
248
  }
249
+ /**
250
+ * Creates a new iterator that invokes the provided callback function over each element of this iterator and skips
251
+ * the elements for which the callback returns `true`, but only for as long as the callback returns `true`.
252
+ *
253
+ * Once the callback function returns `false`, it will no longer be called. The iterator yields the element that
254
+ * caused the callback to return `false`, and well as the subsequent elements.
255
+ *
256
+ * @example
257
+ *
258
+ * ExtraIterator.from(['Alice', 'Antony', 'Charlie', 'Ashley'])
259
+ * .dropWhile(name => name[0] === 'A')
260
+ * .toArray()
261
+ * // returns ['Charlie', 'Ashley']
262
+ */
174
263
  dropWhile(predicate) {
175
264
  return ExtraIterator.from(function* () {
176
265
  for (let index = 0, next; next = this.next(), !next.done; index++) {
@@ -182,6 +271,16 @@ export class ExtraIterator extends Iterator {
182
271
  yield* this;
183
272
  }.call(this));
184
273
  }
274
+ /**
275
+ * Groups the elements in this iterator into arrays of fixed size.
276
+ * The last array might be smaller than the others if the number of elements in this iterator is not divisible by
277
+ * the provided `size` param.
278
+ *
279
+ * @example ExtraIterator.from([1, 2, 3, 4, 5, 6, 7])
280
+ * .chunk(3)
281
+ * .toArray()
282
+ * // returns [[1, 2, 3], [4, 5, 6], [7]]
283
+ */
185
284
  chunk(size) {
186
285
  return ExtraIterator.from(function* () {
187
286
  for (let next; next = this.next(), !next.done;) {
@@ -189,6 +288,18 @@ export class ExtraIterator extends Iterator {
189
288
  }
190
289
  }.call(this));
191
290
  }
291
+ /**
292
+ * Creates a new iterator that iterates on this iterator and the proviuded other iterator, yielding arrays of pairs
293
+ * of elements from this iterator and the other.
294
+ *
295
+ * The elements in this iterator are the first elements of the pairs, and the elements in the other iterator are the
296
+ * second elements of the pairs.
297
+ *
298
+ * @example ExtraIterator.from([1, 2, 3])
299
+ * .zip(['a', 'b', 'c'])
300
+ * .toArray()
301
+ * // returns [[1, 'a'], [2, 'b'], [3, 'c']]
302
+ */
192
303
  zip(other) {
193
304
  return ExtraIterator.from(function* () {
194
305
  const otherIterator = Iterator.from(other);
@@ -197,6 +308,17 @@ export class ExtraIterator extends Iterator {
197
308
  }
198
309
  }.call(this));
199
310
  }
311
+ /**
312
+ * Creates a new iterator that yields the values of this iterator interposed by the provided separator. i.e. The
313
+ * separator is inserted between each pair of subsequent elements of this iterator.
314
+ *
315
+ * @example
316
+ *
317
+ * ExtraIterator.from([1, 2, 3, 4])
318
+ * .interpose('a')
319
+ * .toArray()
320
+ * // returns [1, 'a', 2, 'a', 3, 'a', 4]
321
+ */
200
322
  interpose(separator) {
201
323
  return ExtraIterator.from(function* () {
202
324
  for (let next = this.next(); !next.done;) {
@@ -208,6 +330,17 @@ export class ExtraIterator extends Iterator {
208
330
  }
209
331
  }.call(this));
210
332
  }
333
+ /**
334
+ * Creates a new iterator that yields the values of this iterator and the values of the provided iterator
335
+ * interleaved (alternating). The elements of this iterator always come before the elements of the other iterator.
336
+ *
337
+ * @example
338
+ *
339
+ * ExtraIterator.from([1, 2, 3])
340
+ * .interleave(['a', 'b', 'c'])
341
+ * .toArray()
342
+ * // returns [1, 'a', 2, 'b', 3, 'c']
343
+ */
211
344
  interleave(other) {
212
345
  return ExtraIterator.from(function* () {
213
346
  const otherIterator = ExtraIterator.from(other);
@@ -223,6 +356,20 @@ export class ExtraIterator extends Iterator {
223
356
  }
224
357
  }.call(this));
225
358
  }
359
+ /**
360
+ * Replaces some elements of this iterator with new values.
361
+ *
362
+ * @param startIndex The index of the first element to be replaced.
363
+ * @param deleteCount The number of elements to be replaced.
364
+ * @param newItems The new elements to be inserted.
365
+ *
366
+ * @example
367
+ *
368
+ * ExtraIterator.from([1, 2, 3, 4])
369
+ * .splice(1, 2, 5, 6)
370
+ * .toArray()
371
+ * // returns [1, 5, 6, 4]
372
+ */
226
373
  splice(startIndex, deleteCount, ...newItems) {
227
374
  if (startIndex < 0) {
228
375
  return ExtraIterator.from(this.toArray()
@@ -239,6 +386,9 @@ export class ExtraIterator extends Iterator {
239
386
  }
240
387
  }.call(this));
241
388
  }
389
+ /**
390
+ * Returns an iterator the provided element if this iterator is empty; otherwise, it returns this iterator.
391
+ */
242
392
  defaultIfEmpty(provider) {
243
393
  return ExtraIterator.from(function* () {
244
394
  const result = this.next();
@@ -251,6 +401,90 @@ export class ExtraIterator extends Iterator {
251
401
  }
252
402
  }.call(this));
253
403
  }
404
+ // =================================================================================================================
405
+ // AGGREGATING FUNCTIONS
406
+ // -----------------------------------------------------------------------------------------------------------------
407
+ // These functions consume the iterator and return a new value.
408
+ // =================================================================================================================
409
+ /**
410
+ * Returns the first element of the iterator, or `undefined` if the iterator is empty.
411
+ */
412
+ first() {
413
+ const next = this.next();
414
+ return next.done ? undefined : next.value;
415
+ }
416
+ /**
417
+ * Consumes the iteratror and returns the last value yielded.
418
+ *
419
+ * Returns `undefined` if the iterator is empty.
420
+ */
421
+ last() {
422
+ let previousItem = this.next();
423
+ if (previousItem.done) {
424
+ return undefined;
425
+ }
426
+ for (let currentItem; currentItem = this.next(), !currentItem.done; previousItem = currentItem)
427
+ ;
428
+ return previousItem.value;
429
+ }
430
+ /**
431
+ * Consumes the iterator and returns the value at the provided index.
432
+ */
433
+ at(index) {
434
+ return index === -1 ? this.last()
435
+ : index < 0 ? this.take(index).at(0)
436
+ : this.drop(index).first();
437
+ }
438
+ /**
439
+ * Groups the elements in this iterator into separate arrays and returns an object containing all the groups.
440
+ *
441
+ * The returned object is composed of keys generated by calling the provided callback function on each element of
442
+ * this iterator, and the value for each key is an array containing all the elements that were assigned to that key.
443
+ *
444
+ * @example
445
+ *
446
+ * ExtraIterator.from([1, 2, 3, 4, 5])
447
+ * .groupBy(value => value % 2 === 0 ? 'even' : 'odd')
448
+ * .toArray()
449
+ * // returns { even: [2, 4], odd: [1, 3, 5] }
450
+ */
451
+ groupBy(callbackfn) {
452
+ const result = Object.create(null);
453
+ for (let index = 0, next; next = this.next(), !next.done; index++) {
454
+ const key = callbackfn(next.value, index);
455
+ if (!result[key]) {
456
+ result[key] = [];
457
+ }
458
+ result[key].push(next.value);
459
+ }
460
+ return result;
461
+ }
462
+ toChainOfResponsibilityFunction(invokeHandler) {
463
+ const handlers = this.toArray();
464
+ return (...initialArgs) => {
465
+ const iterator = Iterator.from(handlers);
466
+ function nextFn(...args) {
467
+ const next = iterator.next();
468
+ if (next.done) {
469
+ throw new Error('Chain of responsibility exhausted. No more handlers available.');
470
+ }
471
+ return invokeHandler(next.value, nextFn, ...args);
472
+ }
473
+ ;
474
+ return nextFn(...initialArgs);
475
+ };
476
+ }
477
+ /**
478
+ * Consumes the iterator and returns a value determined by calling the provided function using the iterator as
479
+ * argument.
480
+ *
481
+ * @example
482
+ *
483
+ * ExtraIterator.from(Object.entries({ a: 1, b: 2 }))
484
+ * .map(([key, value]) => ['_' + key, value * 2])
485
+ * .collect(Object.fromEntries)
486
+ * // returns { _a: 2, _b: 4 }
487
+ */
254
488
  collect(collectfn) {
255
489
  return collectfn(this);
256
490
  }
@@ -266,6 +500,9 @@ export class ExtraIterator extends Iterator {
266
500
  return 0;
267
501
  });
268
502
  }
503
+ /**
504
+ * Consumes the iterator and returns the number of elements it contained.
505
+ */
269
506
  count() {
270
507
  let count = 0;
271
508
  for (let next; next = this.next(), !next.done;) {
@@ -273,6 +510,16 @@ export class ExtraIterator extends Iterator {
273
510
  }
274
511
  return count;
275
512
  }
513
+ /**
514
+ * Consumes the iterator and returns a boolean indicating whether all elements in the iterator were unique.
515
+ *
516
+ * If it returns false, then the iterator had at least one duplicated element.
517
+ *
518
+ * @example
519
+ *
520
+ * ExtraIterator.from([1, 2, 3]).uniqueness() // returns true
521
+ * ExtraIterator.from([1, 2, 3, 1]).uniqueness() // returns false
522
+ */
276
523
  uniqueness(mapper) {
277
524
  const seen = new Set();
278
525
  for (let next; next = this.next(), !next.done;) {
@@ -284,4 +531,25 @@ export class ExtraIterator extends Iterator {
284
531
  }
285
532
  return true;
286
533
  }
534
+ // =================================================================================================================
535
+ // MISC FUNCTIONS
536
+ // =================================================================================================================
537
+ /**
538
+ * Lazily executes a function over each element of this iterator as the values are iterated.
539
+ *
540
+ * This method does not change the output values of the iterator.
541
+ *
542
+ * This method is equivalent to {@link map} when the callback function returns the iterated value.
543
+ *
544
+ * This is similar to {@link forEach}, except the callback function is not executed immediately (instead, it is
545
+ * executed when the iterator is iterated), and this method returns the iterator itself.
546
+ */
547
+ withEach(callbackfn) {
548
+ return ExtraIterator.from(function* () {
549
+ for (let index = 0, next; next = this.next(), !next.done; index++) {
550
+ callbackfn(next.value, index);
551
+ yield next.value;
552
+ }
553
+ }.call(this));
554
+ }
287
555
  }
@@ -118,4 +118,23 @@ describe('ExtraIterator', () => {
118
118
  const sum = iterator.collect(iter => Array.from(iter).reduce((a, b) => a + b, 0));
119
119
  expect(sum).toBe(6);
120
120
  });
121
+ it('should create a chain of responsibility function', () => {
122
+ const humanizeDuration = ExtraIterator.from([
123
+ (next, miliseconds) => miliseconds < 1000 ? `${miliseconds} miliseconds` : next(miliseconds / 1000),
124
+ (next, seconds) => seconds < 60 ? `${seconds} seconds` : next(seconds / 60),
125
+ (next, minutes) => minutes < 60 ? `${minutes} minutes` : next(minutes / 60),
126
+ (next, hours) => hours < 24 ? `${hours} hours` : next(hours / 24),
127
+ (_next, days) => `${days} days`,
128
+ ]).toChainOfResponsibilityFunction((handler, next, value) => handler(next, value));
129
+ expect(humanizeDuration(500)).toBe('500 miliseconds');
130
+ expect(humanizeDuration(2000)).toBe('2 seconds');
131
+ expect(humanizeDuration(120000)).toBe('2 minutes');
132
+ expect(humanizeDuration(7200000)).toBe('2 hours');
133
+ expect(humanizeDuration(172800000)).toBe('2 days');
134
+ });
135
+ it('should throw an error if no handlers are available', () => {
136
+ const handlers = ExtraIterator.empty();
137
+ const chain = handlers.toChainOfResponsibilityFunction(next => next());
138
+ expect(() => chain()).toThrow();
139
+ });
121
140
  });
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "An extension of the Iterator class with additional utility helper functions.",
4
4
  "author": "Leonardo Raele <leonardoraele@gmail.com>",
5
5
  "license": "MIT",
6
- "version": "0.4.0",
6
+ "version": "0.4.1",
7
7
  "type": "module",
8
8
  "exports": {
9
9
  ".": "./dist/index.js",