Last active
September 20, 2024 19:36
-
-
Save zelaznik/bbcb63a9b857c1ecedd960a88027098f to your computer and use it in GitHub Desktop.
Implement Ruby's Enumerable.Lazy in Javascript
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 is just a demonstration of how to write an ierator from scratch */ | |
function range(limit) { | |
var i = 0; | |
return { | |
[Symbol.iterator]: function() { | |
return this; | |
}, | |
next: function() { | |
if (i < limit) { | |
var result = { done: false, value: i }; | |
i += 1; | |
return result; | |
} else { | |
return { done: true, value: undefined } | |
} | |
} | |
} | |
} | |
/* | |
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