Skip to content

Instantly share code, notes, and snippets.

@disco0
Forked from nicholasguyett/function-iterator.js
Created October 15, 2020 02:01
Show Gist options
  • Save disco0/7aa0ec900c070ed74bba7001d3171a4c to your computer and use it in GitHub Desktop.
Save disco0/7aa0ec900c070ed74bba7001d3171a4c 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)));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment