This method may not be flawless, but it follows the concept of "creating your own":
You have the option to design a MultipleIterable
interface that only exposes a property indicating whether the object represents an Iterable
with an iterator method that generates a new iterator each time it is called:
export interface MultipleIterable<T> extends Iterable<T> {
multipleIterable: true;
}
In addition, you can define a function that checks if an Iterable
is also a MultipleIterable
:
function isMultableIterable<T>(iterable: Iterable<T>): iterable is MultipleIterable<T> {
return (iterable) && ((iterable as any).multipleIterable === true);
}
If desired, you can extend Array
's prototype to indicate that arrays are MultipleIterable
:
declare global {
interface Array<T> {
multipleIterable: true;
}
}
Array.prototype.multipleIterable = true;
A similar approach could be taken for modifying the prototypes of other built-in iterables exhibiting this behavior.
Next, create a ReplayableIterable<T>
class where the constructor accepts any Iterable<T>
and wraps it so that its iterators always start fresh:
class ReplayableIterable<T> implements MultipleIterable<T> {
multipleIterable = true as true;
[Symbol.iterator](): Iterator<T> {
let cur = 0;
let iterable = this;
return {
next(): IteratorResult<T> {
while (cur >= iterable.iteratorResults.length) {
iterable.iteratorResults.push(iterable.iterator.next());
}
const ret: IteratorResult<T> = iterable.iteratorResults[cur];
cur++;
return ret;
}
}
}
private iterator: Iterator<T>;
private iteratorResults: Array<IteratorResult<T>>;
constructor(iterable: Iterable<T>) {
this.iterator = iterable[Symbol.iterator]();
this.iteratorResults = [];
}
}
The idea is to use the passed-in iterable solely to obtain one iterator, storing the results in an array during processing. The iterators from ReplayableIterable
will only consult the actual iterator if they deplete the array contents. This allows for lazy copying of the iterator - beneficial when dealing with large or infinite iterables to avoid unnecessary memory and time consumption.
Finally, provide a function to convert any Iterable
into a MultipleIterable
, either by returning the unaltered parameter (if it's already a
MultipleIterable</code) or by constructing a <code>ReplayableIterable
based on it:
export function toMultipleIterable<T>(iterable: Iterable<T>): MultipleIterable<T> {
return isMultableIterable(iterable) ? iterable : new ReplayableIterable(iterable);
}
Hopefully, this explanation proves helpful. Best of luck!