jsadvent/lib/Glib.js

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;