Skip to content

Instantly share code, notes, and snippets.

@nicholasguyett
Last active October 15, 2020 02:01
Show Gist options
  • Save nicholasguyett/a699e09b0c1d30a4c71935146d22bc79 to your computer and use it in GitHub Desktop.
Save nicholasguyett/a699e09b0c1d30a4c71935146d22bc79 to your computer and use it in GitHub Desktop.
Functional Iterator
/* This class wraps an iterator/iterable and allows the map/filter/reduce methods
* to be used without being forced to make intermediate temporary arrays.
* I've also implemented the flatten and flatmap functions for convenience
*/
class FunctionalIterator {
constructor(source) {
// Get iterator from source, if present. Otherwise, assume that the source is an iterator
this.source = source[Symbol.iterator] ? source[Symbol.iterator]() : source;
}
// Allow for..of and related constructs to accept the functional iterator directly
[Symbol.iterator]() {
return this;
}
next() {
return this.source.next();
}
// Map method acts exactly like the corresponding array method, but returns another FunctionalIterator instead of an array
map(mappingFunction) {
let self = this;
return new (this.constructor[Symbol.species] || this.constructor)({
next() {
let iteration = self.source.next();
return iteration.done
? iteration
: {
done: false,
value: mappingFunction(iteration.value)
};
}
});
}
// Filter method acts exactly like the corresponding array method, but returns another FunctionalIterator instead of an array
filter(filterFunction) {
let self = this;
return new (this.constructor[Symbol.species] || this.constructor)({
next() {
let iteration = self.source.next();
// Iterate through source until we either find a result that meets the filter or we reach the end of the iterator
while(!(iteration.done || filterFunction(iteration.value))) {
iteration = self.source.next();
};
return iteration;
}
});
}
// Reduce method acts exactly like the corresponding array method, but returns another FunctionalIterator instead of an array
reduce(reduceFunction, seed) {
let iteration = this.source.next();
let accumulator;
// Initialize the accumulator with either the seed value or the first value of the iteration
if (seed) {
accumulator = seed;
}
else {
accumulator = iteration.value;
iteration = this.source.next();
}
for (; !iteration.done; iteration = this.source.next()) {
accumulator = reduceFunction(accumulator, iteration.value);
}
return accumulator;
}
*_flatten(depth) {
if(depth < 0) throw new TypeError('Depth cannot be negative');
if (depth === 0) return this; // Avoid unnecessary work
for(let iteration = this.source.next(); !iteration.done; iteration = this.source.next()) {
if(iteration.value && iteration.value[Symbol.iterator]) {
// Item is iterable, so lets flatten it
if(depth > 1) {
// Need to go deeper, so recursively flatten the sub-arrays
yield* new FunctionalIterator(iteration.value).flatten(depth - 1);
}
else {
// Depth is 1 or undefined (which means we default to 1), so we only flatten the next layer
yield* iteration.value;
}
}
else {
// Nothing to flatten if the value is not iterable
yield iteration.value;
}
}
}
flatten() {
return new (this.constructor[Symbol.species] || this.constructor)(this._flatten());
}
flatMap(mappingFunction, thisArg) {
return this.map(mappingFunction, thisArg).flatten();
}
*_concat(other) {
yield* this;
yield* other;
}
concat(other) {
return new (this.constructor[Symbol.species] || this.constructor)(this._concat(other));
}
some(callback) {
let iteration = this.source.next();
let result = false;
while(!(iteration.done || result)) {
result = callback(iteration.value);
}
return result;
}
includes(searchElement) {
return this.some(item => item === searchElement || (Number.isNaN(item) && Number.isNaN(searchElement)));
}
}
@nicholasguyett
Copy link
Author

Implemented some and includes

@nicholasguyett
Copy link
Author

Added support for Symbol.species, in case someone wants to subclass this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment