const Math = require('./bnmath'); const EMPTY = Symbol('empty'); class Glib { // NB: this assumes all inputs are iterable static concat(...args) { return Glib.fromIterable(args).flatMap((i) => { return i; }); } static infiniteFromIterable(iterable) { const cache = Array.from(iterable); return new Glib( (function*() { while (true) { yield* cache; } })(), ); } static fromSplit(string, separator = '\n') { return new Glib( (function*() { let partial = ''; for (const char of string) { partial += char; if (!partial.endsWith(separator)) { continue; } yield partial.slice(0, -separator.length); partial = ''; } yield partial; })(), ); } static fromParsedString(string, separator, parse) { return new Glib( (function*() { let partial = ''; for (const char of string) { partial += char; if (!partial.endsWith(separator)) { continue; } yield parse(partial.slice(0, -separator.length)); partial = ''; } yield parse(partial); })(), ); } static fromLines(lines) { const g = Glib.fromIterable(lines.split('\n')); return g.map((line) => { // console.log(line); return line.trim(); }); } static fromSequence(start = 1n, end = Infinity, step = 1n) { if (typeof start === 'number') { start = BigInt(start); } if (typeof end === 'number' && (end !== Infinity && end !== -Infinity)) { end = BigInt(end); } if (typeof step === 'number') { step = BigInt(step); } return new Glib( (function*() { for (let i = start; i <= end; i += step) { yield i; } })(), ); } static fromIterable(iterable) { return new Glib( (function*() { yield* iterable; })(), ); } constructor(iterator) { this._ = iterator; } flatMap(mapFn) { return new Glib( (function*(iterable) { let index = 0n; for (const entry of iterable) { yield* mapFn(entry, index++); } })(this), ); } map(mapFn) { return this.flatMap((...args) => { return [mapFn(...args)]; }); } filter(filterFn) { return this.flatMap((entry, ...args) => filterFn(entry, ...args) ? [entry] : [], ); } reduce(reduceFn, accumulator = EMPTY) { return this.partialReduce(reduceFn, () => true, accumulator); } join(sep = ',') { return this.reduce((acc, e, i) => acc + (i === 0 ? e : sep + e), ''); } partialReduce(reduceFn, continueFn, accumulator = EMPTY) { let index = 0; for (const entry of this) { // console.log(accumulator); if (index === 0 && accumulator === EMPTY) { accumulator = entry; } else { // console.log(`${accumulator} ${JSON.stringify(entry)}`); accumulator = reduceFn(accumulator, entry, index++); if (!continueFn(accumulator, entry, index - 1)) { break; } } } // console.log(accumulator); if (index === 0 && accumulator === EMPTY) { throw new Error( 'reduce of empty Glib with no starting value for accumulator', ); } return accumulator; } take(count = 1) { return new Glib( (function*(iterable) { let taken = 0; if (taken >= count) { return; } for (const entry of iterable) { yield entry; if (++taken >= count) { return; } } })(this), ); } first() { for (const entry of this) { return entry; } } toInts(base = 10) { switch (base) { case 10: return this.map((i) => BigInt(i)); default: throw new Error('unimplemented base'); } } sum() { return this.reduce((a, b) => { return a + b; }, 0n); } product() { return this.partialReduce( (a, b) => { return a * b; }, (p) => p !== 0n, ); } min() { return this.reduce((a, b) => (a < b ? a : b)); } max() { return this.reduce((a, b) => (a > b ? a : b)); } minMax() { return this.reduce( ([min, max], current) => { return [Math.min(min, current), Math.max(max, current)]; }, [Infinity, -Infinity], ); } then(fn) { return fn(this); } infinite() { return Glib.infiniteFromIterable(this); } forEach(eachFn) { let i = BigInt(0); for (let entry of this) { eachFn(entry, i); i++; } } chunk(chunkLength = EMPTY, leftoversOk = false) { if (chunkLength === EMPTY) { throw new Error('must provide chunk length'); } return new Glib( (function*(iterable) { let chunk = []; for (const entry of iterable) { chunk.push(entry); if (chunk.length === chunkLength) { yield chunk; chunk = []; } } if (chunk.length !== 0) { if (leftoversOk) { yield chunk; } else { throw new Error('chunk operation had leftovers'); } } })(this), ); } frequency() { return this.reduce((counts, k) => { counts.set(k, (counts.get(k) || 0n) + 1n); return counts; }, new Map()); } every(everyFn) { return this.map(everyFn) .filter((i) => i === false) .isEmpty(); } lookupInMap(map, allowMissing = false) { if (!allowMissing) { return this.map((k) => { if (!map.has(k)) { throw new Error(`missing value ${k.string}`); } return map.get(k); }); } return this.flatMap((k) => { // console.log(k); return map.has(k) ? [map.get(k)] : []; }); } filterBySet(set, dropBySet = false) { if (dropBySet) { return this.filter((k) => !set.has(k)); } return this.filter((k) => set.has(k)); } unique() { const seen = new Set(); // better than converting whole iterable to set // allows for iterative/partial compoutation // normal set creation from iterable is eager return this.filter((e) => { if (seen.has(e)) { return false; } seen.add(e); return true; }); } skip(n = 1n) { return this.filter((_, i) => i >= n); } drop(n = 1n) { return this.filter((_, i) => i >= n); } // NB: this assumes all inputs are iterable concat(...iterables) { return Glib.concat(this, ...iterables); } [Symbol.iterator]() { return this._; } get array() { return Array.from(this); } get string() { return this.join(''); } get set() { return new Set(this); } get length() { let i = 0n; for (const _ of this) { i++; } return i; } isEmpty() { for (const entry of this) { return false; } return true; } toMap() { return new Map(this); } // need dvFn to not resuse sets, arrays, etc toMapReduce(reduceFn, dvFn) { const output = new Map(); for (const [k, v] of this) { output.set(k, reduceFn(output.has(k) ? output.get(k) : dvFn(), v)); } return output; } toMapArray() { return this.toMapReduce( (acc, a) => { return [...acc, a]; }, () => [], ); } toMapSet() { return this.toMapReduce( (acc, a) => { acc.add(a); return acc; }, () => new Set(), ); } reduceScoreK(chooseFn) { return this.reduce((a, b) => (chooseFn(a[1], b[1]) ? a : b))[0]; } minScoreK() { return this.reduceScoreK((a, b) => a < b); } maxScoreK() { return this.reduceScoreK((a, b) => a > b); } reduceScore(chooseFn) { return this.reduce((a, b) => (chooseFn(a[1], b[1]) ? a : b)); } minScore() { return this.reduceScore((a, b) => a < b); } maxScoreK() { return this.reduceScore((a, b) => a > b); } } module.exports = Glib;