Last active
October 15, 2020 02:01
-
-
Save nicholasguyett/a699e09b0c1d30a4c71935146d22bc79 to your computer and use it in GitHub Desktop.
Functional Iterator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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))); | |
} | |
} |
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
Implemented some and includes