Sane enumerators for JavaScript using Generators
// for compatibility with bignumbers/regular numbers | |
// getZero(5n) = 0n | |
// getZero(5) = 0 | |
let getZero = function (entity) { | |
return entity - entity; | |
} | |
let getOne = function (entity) { | |
let zero = getZero(entity); | |
zero++; | |
return zero; | |
} | |
class Enumerator { | |
constructor(gen) { | |
this.generator = gen; | |
} | |
static range(...args) { | |
return new Enumerator.Range(...args); | |
} | |
forEach(fn) { | |
let onceGen = this.generator(); | |
for(let el of onceGen) { | |
fn(el); | |
} | |
} | |
get length() { | |
return toArray().length; | |
} | |
empty() { | |
let onceGen = this.generator(); | |
return onceGen.next().done; | |
} | |
map(fn) { | |
return new Enumerator(function* () { | |
let onceGen = this.generator(); | |
for(let el of onceGen) { | |
yield fn(el); | |
} | |
}.bind(this)); | |
} | |
withIndex(start = 0) { | |
return new Enumerator(function* () { | |
let onceGen = this.generator(); | |
let i = start; | |
for(let el of onceGen) { | |
yield [el, i]; | |
i++; | |
} | |
}.bind(this)); | |
} | |
takeWhile(fn) { | |
return new Enumerator(function* () { | |
let onceGen = this.generator(); | |
for(let el of onceGen) { | |
if(!fn(el)) { | |
break; | |
} | |
yield el; | |
} | |
}.bind(this)); | |
} | |
dropWhile(fn) { | |
return new Enumerator(function* () { | |
let onceGen = this.generator(); | |
let started = false; | |
let el, val; | |
do { | |
el = onceGen.next(); | |
val = el.value; | |
if(started) { | |
yield val; | |
} | |
else { | |
started = !fn(val); | |
} | |
} while(!el.done); | |
}.bind(this)); | |
} | |
toArray() { | |
let res = []; | |
this.forEach(el => res.push(el)); | |
return res; | |
} | |
get(index) { | |
let onceGen = this.generator(); | |
let el; | |
do { | |
el = onceGen.next(); | |
} while(index --> 0); | |
return el.value; | |
} | |
first(n = Enumerator.NO_PARAMETER) { | |
if(n == Enumerator.NO_PARAMETER) { | |
return this.get(0); | |
} | |
else { | |
return this.withIndex() | |
.takeWhile(([e, i]) => i < n) | |
.map(([e, i]) => e); | |
} | |
} | |
reduce(fn, seed = Enumerator.NO_PARAMETER) { | |
let onceGen = this.generator(); | |
let acc = onceGen.next(); | |
if(acc.done) { | |
return seed === Enumerator.NO_PARAMETER ? undefined : seed; | |
} | |
acc = acc.value; | |
let cur = onceGen.next(); | |
while(!cur.done) { | |
acc = fn(acc, cur.value); | |
cur = onceGen.next(); | |
} | |
return acc; | |
} | |
sum(fn = x => x, zero = 0) { | |
if(this.empty()) { | |
return zero; | |
} | |
return this.map(fn).reduce((a, b) => a + b); | |
} | |
toString() { | |
return "[" + this.toArray().join(", ") + "]"; | |
} | |
} | |
Enumerator.NO_PARAMETER = Symbol("Enumerator.NO_PARAMETER"); | |
Enumerator.Range = class extends Enumerator { | |
constructor(lower, upper = Enumerator.NO_PARAMETER, step = Enumerator.NO_PARAMETER) { | |
if(upper === Enumerator.NO_PARAMETER) { | |
upper = lower; | |
lower = getZero(upper); | |
} | |
if(step === Enumerator.NO_PARAMETER) { | |
step = getOne(upper); | |
} | |
super(function* () { | |
for(let i = lower; i < upper; i += step) { | |
yield i; | |
} | |
}); | |
this.lower = lower; | |
this.upper = upper; | |
} | |
get(index) { | |
let val = this.lower + index * step; | |
return val < this.upper ? val : undefined; | |
} | |
} | |
Enumerator.Nats = Enumerator.range(1n, Infinity); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment