358 lines
7.9 KiB
JavaScript
358 lines
7.9 KiB
JavaScript
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;
|