Skip to content

Instantly share code, notes, and snippets.

@zelaznik
Last active December 5, 2020 16:04
Show Gist options
  • Save zelaznik/bbcb63a9b857c1ecedd960a88027098f to your computer and use it in GitHub Desktop.
Save zelaznik/bbcb63a9b857c1ecedd960a88027098f to your computer and use it in GitHub Desktop.
Implement Ruby's Enumerable.Lazy in Javascript
/*
This is an attempt to implement the behavior of Ruby's
lazy enumerator, but in javascript. The main reason for
the exercise was to see if I could make an object that would
work with the [...someCustomObject] pattern.
*/
function* enumerate(context) {
let index = 0;
for (const value of context) {
yield [value, index];
index++;
}
}
function isPrimitive(value) {
return value == null || /^[sbn]/.test(typeof value);
}
class Enumerable {
reduce(callback, seed) {
const iterator = enumerate(this);
let accumulation;
if (arguments.length > 1) {
accumulation = seed;
} else {
[accumulation] = iterator.next().value;
}
for (const [value, index] of iterator) {
accumulation = callback(accumulation, value, index);
}
return accumulation;
}
toArray() {
return this.reduce((agg, value) => agg.push(value) && agg, []);
}
length() {
return this.reduce((agg) => agg + 1, 0);
}
max() {
return this.reduce((a, b) => Math.max(a, b));
}
min() {
return this.reduce((a, b) => Math.min(a, b));
}
flat() {
return new Enumerator(
function* () {
for (const value of this) {
if (isPrimitive(value)) {
yield value;
} else {
for (const nestedValue of new Enumerator(value).flat()) {
yield nestedValue;
}
}
}
}.bind(this)
);
}
find(criterion) {
for (const [value, index] of enumerate(this)) {
if (criterion(value, index)) {
return value;
}
}
}
indexOf(expectedValue) {
for (const [value, index] of enumerate(this)) {
if (value === expectedValue) {
return index;
}
}
return -1;
}
lastIndexOf(expectedValue) {
let highestIndexSoFar = -1;
for (const [value, index] of enumerate(this)) {
if (value === expectedValue) {
highestIndexSoFar = index;
}
}
return highestIndexSoFar;
}
includes(expectedValue) {
return this.any((value) => value === expectedValue);
}
any(criterion) {
for (const [value, index] of enumerate(this)) {
if (criterion(value, index)) {
return true;
}
}
return false;
}
every(criterion) {
for (const [value, index] of enumerate(this)) {
if (!criterion(value, index)) {
return false;
}
}
return true;
}
map(transform) {
return new Enumerator(
function* () {
for (const [value, index] of enumerate(this)) {
yield transform(value, index);
}
}.bind(this)
);
}
filter(criterion) {
return new Enumerator(
function* () {
for (const [value, index] of enumerate(this)) {
if (criterion(value, index)) {
yield value;
}
}
}.bind(this)
);
}
withIndex() {
return new Enumerator(() => enumerate(this));
}
zip(otherEnumerable) {
return new Enumerator(
function* () {
let otherIterator = new Enumerator(otherEnumerable)[Symbol.iterator]();
for (const value of this) {
let otherItem = otherIterator.next();
if (otherItem.done) {
break;
} else {
yield [value, otherItem.value];
}
}
}.bind(this)
);
}
take(quantity) {
return new Enumerator(
function* () {
for (const [value, index] of enumerate(this)) {
if (index < quantity) {
yield value;
} else {
break;
}
}
}.bind(this)
);
}
drop(quantity) {
return new Enumerator(
function* () {
for (const [value, index] of enumerate(this)) {
if (index >= quantity) {
yield value;
}
}
}.bind(this)
);
}
slice(start, count) {
return this.drop(start).take(count);
}
}
class Enumerator extends Enumerable {
constructor(enumerable) {
super();
Object.defineProperty(this, "_enumerable", { value: enumerable });
}
[Symbol.iterator]() {
return this._enumerable[Symbol.iterator]
? this._enumerable[Symbol.iterator]()
: this._enumerable();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment