@alexaegis/advent-of-code-lib
Version:
Advent of Code Library
1,586 lines • 103 kB
JavaScript
import { addAllToSet } from "./set.js";
import { isNotNullish } from "@alexaegis/common";
import { NUM, NEWLINE } from "./regex.js";
import { PriorityQueue } from "js-sdsl";
import "./index-41yGvuWH.js";
import "./map.js";
import "node:perf_hooks";
import "node:fs/promises";
import "node:path";
import "node:fs";
import "kolorist";
import { type } from "arktype";
const arrayContains = (array, item) => {
return hasEquals(item) ? array.some((i) => item.equals(i)) : array.includes(item);
};
const hasEquals = (o) => {
return typeof o.equals === "function";
};
const arrayDiff = (first, second) => {
const onlyInFirst = [];
const onlyInSecond = [];
const bothHas = [];
for (const itemFromFirst of first) {
if (arrayContains(second, itemFromFirst)) {
bothHas.push(itemFromFirst);
} else {
onlyInFirst.push(itemFromFirst);
}
}
for (const itemFromSecond of second) {
if (!arrayContains(first, itemFromSecond)) {
onlyInSecond.push(itemFromSecond);
}
}
return {
onlyInFirst,
onlyInSecond,
bothHas
};
};
const isNumber = (n) => typeof n === "number";
const isNumberArray = (n) => n.every(isNumber);
const isBigint = (n) => typeof n === "bigint";
const isNumeric = (n) => isNumber(n) || isBigint(n);
const max$1 = (a, b) => a < b ? b : a;
const min$1 = (a, b) => a > b ? b : a;
const sum = (a, b) => a + b;
const mult = (a, b) => a * b;
const dup = (a) => a * 2;
const add = (a, b) => a + b;
const sub = (a, b) => a - b;
const descending = (a, b) => b - a;
const ascending = sub;
const findEndOfPair = (t, [begin, end], from = 0) => {
if (t[from] !== begin) {
throw new Error("First element must be the opening part of the pair");
}
let pc = 1;
let i = from + 1;
for (; i < t.length; i++) {
if (t[i] === begin)
pc++;
else if (t[i] === end)
pc--;
if (pc === 0)
break;
}
if (pc !== 0) {
return void 0;
}
return i;
};
const cutSubSegment = (t, pair, from = 0, leaveOneBehind = false) => {
const j = findEndOfPair(t, pair, from);
if (j === void 0) {
return void 0;
} else {
const inner = t.splice(from + 1, j - from - 1);
if (leaveOneBehind) {
t.splice(from + 1, 1);
} else {
t.splice(from, 2);
}
return inner;
}
};
const nonNullish = (t) => t !== void 0 && t !== null;
const filterMap = (array, mapFn) => {
const result = [];
let i = 0;
for (const item of array) {
const value = mapFn(item, i);
if (nonNullish(value)) {
result.push(value);
}
i++;
}
return result;
};
const findLast = (array, predicate, skipCount = 0) => {
for (let i = array.length - 1 - skipCount; i >= 0; i--) {
if (predicate(array[i], i)) {
return array[i];
}
}
return void 0;
};
function* matrixFlipFlop(matrix) {
let state = matrix;
yield state = state.rotateMatrix("r");
yield state = state.rotateMatrix("r");
yield state = state.rotateMatrix("r");
yield state = state.rotateMatrix("r");
yield state = state.flipMatrix("x");
yield state = state.rotateMatrix("r");
yield state = state.rotateMatrix("r");
yield state = state.rotateMatrix("r");
yield state = state.rotateMatrix("r");
}
const flipMatrix = (matrix, axis = "x") => {
if (matrix.length === 0) {
return [];
} else if (!Array.isArray(matrix[0]) && axis !== "y") {
throw new Error("Input is not a matrix, cannot be flipped");
}
matrix = axis === "x" ? [...matrix].reverse() : matrix.map((row) => [...row].reverse());
return matrix;
};
const getSizedGroups = (array, groupSize) => array.reduce(
(groups, next) => {
let lastGroup = groups.at(-1);
if (lastGroup) {
if (lastGroup.length >= groupSize) {
lastGroup = [];
groups.push(lastGroup);
}
lastGroup.push(next);
}
return groups;
},
[[]]
);
const groupByDelimiter = (values, isDelimiter = (t) => !t) => {
const result = [[]];
for (const value of values) {
if (isDelimiter(value)) {
result.push([]);
} else {
result.at(-1)?.push(value);
}
}
return result;
};
const pairwise = (array, callback) => {
let last = void 0;
for (const element of array) {
if (last !== void 0) {
callback(last, element);
}
last = element;
}
};
const slideWindow = (array, windowSize = 2, stepSize = 1) => {
const result = [];
const window = [];
for (const element of array) {
window.push(element);
if (window.length === windowSize) {
result.push([...window]);
window.splice(0, stepSize);
}
}
return result;
};
const mapFirst = (array, map) => {
for (const item of array) {
const value = map(item);
if (value) {
return value;
}
}
return void 0;
};
const mapLast = (array, map) => {
for (let i = array.length - 1; i >= 0; i--) {
const value = map(array[i]);
if (value) {
return value;
}
}
return void 0;
};
const maxOf = (array, count = 1) => {
return count === 1 ? array.reduce(max$1, Number.NEGATIVE_INFINITY) : [...array].sort(descending).splice(0, count);
};
const addWithinRange = (base, add2, fromOrTo, optionalTo) => {
if (nonNullish(fromOrTo)) {
const [from, to] = nonNullish(optionalTo) ? [fromOrTo, optionalTo] : [0, fromOrTo];
const range = to - from + 1;
return (base + add2 - from) % range + from;
} else {
return base + add2;
}
};
const posMod = (n, m) => {
const mod = n % m;
return Math.abs(mod >= 0 ? mod : (mod + m) % m);
};
const posModBigInt = (n, m) => {
const mod = n % m;
return mod >= 0 ? mod : (mod + m) % m;
};
const egcd = (a, b) => {
if (a === 0) {
return [b, 0, 1];
} else {
const [gcd2, y, x] = egcd(posMod(b, a), a);
return [gcd2, x - b / a * y, y];
}
};
const egcdBigInt = (a, b) => {
if (a === 0n) {
return [b, 0n, 1n];
} else {
const [g, y, x] = egcdBigInt(posModBigInt(b, a), a);
return [g, x - b / a * y, y];
}
};
const gcdOverTwo = (x = 0, y = 0) => {
x = Math.abs(x);
y = Math.abs(y);
while (y) {
const t = y;
y = x % y;
x = t;
}
return x;
};
const gcd = (x, y) => Array.isArray(x) ? x.reduce((a, n) => gcdOverTwo(a, n), 1) : gcdOverTwo(x, y);
var DirectionArrowSymbol = /* @__PURE__ */ ((DirectionArrowSymbol2) => {
DirectionArrowSymbol2["NORTH"] = "^";
DirectionArrowSymbol2["EAST"] = ">";
DirectionArrowSymbol2["SOUTH"] = "v";
DirectionArrowSymbol2["WEST"] = "<";
return DirectionArrowSymbol2;
})(DirectionArrowSymbol || {});
var DirectionNames = /* @__PURE__ */ ((DirectionNames2) => {
DirectionNames2["NORTH"] = "NORTH";
DirectionNames2["EAST"] = "EAST";
DirectionNames2["SOUTH"] = "SOUTH";
DirectionNames2["WEST"] = "WEST";
return DirectionNames2;
})(DirectionNames || {});
const isHorizontalDirectionArrowSymbol = (symbol) => symbol === ">" || symbol === "<";
const isVericalDirectionArrowSymbol = (symbol) => symbol === "^" || symbol === "v";
const isDirectionArrowSymbol = (symbol) => isHorizontalDirectionArrowSymbol(symbol) || isVericalDirectionArrowSymbol(symbol);
var DirectionCardinalGeographicLetter = /* @__PURE__ */ ((DirectionCardinalGeographicLetter2) => {
DirectionCardinalGeographicLetter2["NORTH"] = "N";
DirectionCardinalGeographicLetter2["EAST"] = "E";
DirectionCardinalGeographicLetter2["SOUTH"] = "S";
DirectionCardinalGeographicLetter2["WEST"] = "W";
return DirectionCardinalGeographicLetter2;
})(DirectionCardinalGeographicLetter || {});
const isHorizontalDirectionCardinalGeographicLetter = (symbol) => symbol === "E" || symbol === "W";
const isVericalDirectionCardinalGeographicLetter = (symbol) => symbol === "N" || symbol === "S";
const isDirectionCardinalGeographicLetter = (marker) => isHorizontalDirectionCardinalGeographicLetter(marker) || isVericalDirectionCardinalGeographicLetter(marker);
var DirectionCardinalLiteralLetter = /* @__PURE__ */ ((DirectionCardinalLiteralLetter2) => {
DirectionCardinalLiteralLetter2["NORTH"] = "U";
DirectionCardinalLiteralLetter2["EAST"] = "R";
DirectionCardinalLiteralLetter2["SOUTH"] = "D";
DirectionCardinalLiteralLetter2["WEST"] = "L";
return DirectionCardinalLiteralLetter2;
})(DirectionCardinalLiteralLetter || {});
const isHorizontalDirectionCardinalLiteralLetter = (symbol) => symbol === "R" || symbol === "L";
const isVericalDirectionCardinalLiteralLetter = (symbol) => symbol === "U" || symbol === "D";
const isDirectionCardinalLiteralLetter = (marker) => isHorizontalDirectionCardinalLiteralLetter(marker) || isVericalDirectionCardinalLiteralLetter(marker);
const isDirectionMarker = (marker) => isDirectionArrowSymbol(marker) || isDirectionCardinalGeographicLetter(marker) || isDirectionCardinalLiteralLetter(marker);
class Vec2 {
static ORIGIN = Object.freeze(new Vec2(0, 0));
static INIFINITY_NE = Object.freeze(
new Vec2(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY)
);
static INIFINITY_NW = Object.freeze(
new Vec2(Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY)
);
static INIFINITY_SE = Object.freeze(
new Vec2(Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY)
);
static INIFINITY_SW = Object.freeze(
new Vec2(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY)
);
x;
y;
constructor(x, y) {
if (typeof x === "object") {
this.x = x.x;
this.y = x.y;
} else if (typeof x === "number" && typeof y === "number") {
this.x = x;
this.y = y;
} else if (typeof x === "string") {
if (isDirectionMarker(x)) {
switch (x) {
case DirectionArrowSymbol.NORTH:
case DirectionCardinalLiteralLetter.NORTH:
case DirectionCardinalGeographicLetter.NORTH: {
this.x = 0;
this.y = 1;
break;
}
case DirectionArrowSymbol.EAST:
case DirectionCardinalLiteralLetter.EAST:
case DirectionCardinalGeographicLetter.EAST: {
this.x = 1;
this.y = 0;
break;
}
case DirectionArrowSymbol.SOUTH:
case DirectionCardinalLiteralLetter.SOUTH:
case DirectionCardinalGeographicLetter.SOUTH: {
this.x = 0;
this.y = -1;
break;
}
case DirectionArrowSymbol.WEST:
case DirectionCardinalLiteralLetter.WEST:
case DirectionCardinalGeographicLetter.WEST: {
this.x = -1;
this.y = 0;
break;
}
}
} else {
[this.x, this.y] = (x.match(NUM) ?? []).map((s) => Number.parseInt(s, 10));
}
}
}
static compareColumnFirst(a, b) {
return a.x === b.x ? a.y - b.y : a.x - b.x;
}
/**
* Negative if b is before
*/
static compareRowFirst(a, b) {
return a.y === b.y ? a.x - b.x : a.y - b.y;
}
compareColumnFirst(o) {
return Vec2.compareColumnFirst(this, o);
}
compareRowFirst(o) {
return Vec2.compareRowFirst(this, o);
}
static comparator(a, b) {
return a.y === b.y ? a.x - b.x : a.y - b.y;
}
static sort(a) {
return a.sort(Vec2.comparator);
}
static isWithin(v, area) {
return area.horizontal.contains(v.x) && area.vertical.contains(v.y);
}
isWithin(area) {
return Vec2.isWithin(this, area);
}
static isFinite(v, partial = false) {
if (typeof partial === "string") {
const x = partial === "x" ? Number.isFinite(v.x) : true;
const y = partial === "y" ? Number.isFinite(v.y) : true;
return x && y;
} else if (partial) {
return Number.isFinite(v.x) || Number.isFinite(v.y);
} else {
return Number.isFinite(v.x) && Number.isFinite(v.y);
}
}
isFinite(partial = false) {
return Vec2.isFinite(this, partial);
}
clamp(area) {
const xMax = Math.max(area.topLeft.x, area.bottomRight.x);
const yMax = Math.max(area.topLeft.y, area.bottomRight.y);
const xMin = Math.min(area.topLeft.x, area.bottomRight.x);
const yMin = Math.min(area.topLeft.y, area.bottomRight.y);
if (this.x > xMax) {
this.x = xMax;
} else if (this.x < xMin) {
this.x = xMin;
}
if (this.y > yMax) {
this.y = yMax;
} else if (this.y < yMin) {
this.y = yMin;
}
return this;
}
add(coord, options) {
return this.clone().addMut(coord, options);
}
*generateVectorsAroundInManhattanRadius(radius) {
if (radius === 0) {
yield this;
return;
}
const leftEdge = this.x - radius;
const rightEdge = this.x + radius;
yield new Vec2(leftEdge, this.y);
yield new Vec2(rightEdge, this.y);
yield new Vec2(this.x, this.y + radius);
yield new Vec2(this.x, this.y - radius);
for (let i = leftEdge + 1; i < this.x; i++) {
const yDiff = i - leftEdge;
yield new Vec2(leftEdge + i, this.y + yDiff);
yield new Vec2(leftEdge + i, this.y - yDiff);
yield new Vec2(rightEdge - i, this.y + yDiff);
yield new Vec2(rightEdge - i, this.y - yDiff);
}
}
addMut(v, options) {
const originalX = this.x;
const originalY = this.y;
const diffX = v.x * (options?.times ?? 1);
const diffY = v.y * (options?.times ?? 1);
this.x += options?.flipX ? -diffX : diffX;
this.y += options?.flipY ? -diffY : diffY;
if (options?.limit && (typeof options.limit === "function" ? options.limit(this) : !Vec2.isWithin(this, options.limit))) {
this.x = originalX;
this.y = originalY;
}
return this;
}
sub(o, times = 1) {
let ox = o.x;
if (ox === Number.NEGATIVE_INFINITY) {
ox = 0;
}
let oy = o.y;
if (oy === Number.NEGATIVE_INFINITY) {
oy = 0;
}
return new Vec2(this.x - ox * times, this.y - oy * times);
}
subMut(o, times = 1) {
this.x -= o.x * times;
this.y -= o.y * times;
return this;
}
manhattan(x, y) {
if (typeof x === "number" && typeof y === "number") {
return Math.abs(x - this.x) + Math.abs(y - this.y);
} else if (typeof x === "object") {
return this.manhattan(x.x, x.y);
} else {
return 0;
}
}
dist(o) {
return Math.sqrt(Math.pow(o.x - this.x, 2) + Math.pow(o.y - this.y, 2));
}
stepVec(to) {
const dx = to.x - this.x;
const dy = to.y - this.y;
let g = gcd(dx, dy);
const step = new Vec2(dx / g, dy / g);
while (g !== 1) {
g = gcd(step.x, step.y);
step.x /= g;
step.y /= g;
}
return step;
}
isInt() {
return Math.floor(this.x) === this.x && Math.floor(this.y) === this.y;
}
floor() {
this.x = Math.floor(this.x);
this.y = Math.floor(this.y);
return this;
}
ceil() {
this.x = Math.ceil(this.x);
this.y = Math.ceil(this.y);
return this;
}
/**
* TODO: remove duplicate method
* @param o
* @returns
*/
subtract(o) {
const dx = o.x - this.x;
const dy = o.y - this.y;
return new Vec2(dx, dy);
}
subtractMut(o) {
this.x = o.x - this.x;
this.y = o.y - this.y;
return this;
}
negateMut() {
this.x = -this.x;
this.y = -this.y;
return this;
}
negate() {
return new Vec2(-this.x, -this.y);
}
*reach(o, yieldStart = false, yieldEnd = false) {
const stepVec = this.stepVec(o);
const current = this.add(stepVec);
if (yieldStart) {
yield this.clone();
}
while (!current.equals(o)) {
yield current.clone();
current.addMut(stepVec);
}
if (yieldEnd) {
yield current.clone();
}
}
los(f) {
return f.filter((fo) => !fo.equals(this)).map(
(o) => [...this.reach(o, false, true)].filter((l) => f.find((fi) => fi.equals(l))).sort((a, b) => this.dist(a) - this.dist(b)).shift()
).filter((a) => !!a).reduce((acc, n) => {
if (!acc.some((a) => a.equals(n))) {
acc.push(n);
}
return acc;
}, []);
}
equals(o) {
return this.x === o.x && this.y === o.y;
}
angle(o) {
return Math.atan2(o.y - this.y, o.x - this.x) * 180 / Math.PI;
}
toString() {
return Vec2.toString(this);
}
static toString(v) {
return typeof v === "string" ? v : `${v.x},${v.y}`;
}
clone() {
return new Vec2(this);
}
rotateLeft(times = 1, around = Vec2.ORIGIN) {
this.subMut(around);
for (let i = 0; i < times; i++) {
const x = this.x;
this.x = -this.y;
this.y = x;
this.addMut(around);
}
return this;
}
rotateRight(times = 1, around = Vec2.ORIGIN) {
this.subMut(around);
for (let i = 0; i < times; i++) {
const y = this.y;
this.y = -this.x;
this.x = y;
this.addMut(around);
}
return this;
}
isNeighbour(o) {
return Math.abs(this.x - o.x) <= 1 && Math.abs(this.y - o.y) <= 1;
}
normalizeMut() {
this.x = Math.max(Math.min(this.x, 1), -1);
this.y = Math.max(Math.min(this.y, 1), -1);
return this;
}
set(o) {
this.x = o.x;
this.y = o.y;
return this;
}
applyChange(fn) {
this.x = fn(this.x);
this.y = fn(this.y);
return this;
}
middle(o) {
const result = this.add(o);
result.x = result.x / 2;
result.y = result.y / 2;
return result;
}
}
const INTERVAL_ENDPOINT_OPEN_QUALIFIER = "open";
const INTERVAL_ENDPOINT_CLOSED_QUALIFIER = "closed";
const INTERVAL_CLOSED = {
lowQualifier: INTERVAL_ENDPOINT_CLOSED_QUALIFIER,
highQualifier: INTERVAL_ENDPOINT_CLOSED_QUALIFIER
};
const INTERVAL_CLOSED_OPEN = {
lowQualifier: INTERVAL_ENDPOINT_CLOSED_QUALIFIER,
highQualifier: INTERVAL_ENDPOINT_OPEN_QUALIFIER
};
const INTERVAL_OPEN = {
lowQualifier: INTERVAL_ENDPOINT_OPEN_QUALIFIER,
highQualifier: INTERVAL_ENDPOINT_OPEN_QUALIFIER
};
const INTERVAL_OPEN_CLOSED = {
lowQualifier: INTERVAL_ENDPOINT_OPEN_QUALIFIER,
highQualifier: INTERVAL_ENDPOINT_CLOSED_QUALIFIER
};
class Interval {
low;
high;
/**
* @default 'open'
*/
lowQualifier;
/**
* @default 'closed'
*/
highQualifier;
/**
* Contains only one element, 0
*/
static ZERO = new Interval(0, 0, INTERVAL_CLOSED);
/**
* Contains no elements
*/
static EMPTY = new Interval(0, 0, INTERVAL_OPEN);
static INFINITE = new Interval(
Number.NEGATIVE_INFINITY,
Number.POSITIVE_INFINITY,
INTERVAL_OPEN
);
constructor(low, high, options = INTERVAL_CLOSED_OPEN) {
this.low = Math.min(low, high);
this.high = Math.max(low, high);
this.lowQualifier = options.lowQualifier ?? INTERVAL_ENDPOINT_CLOSED_QUALIFIER;
this.highQualifier = options.highQualifier ?? INTERVAL_ENDPOINT_OPEN_QUALIFIER;
}
static invertQualifier(qualifier) {
return qualifier === "open" ? "closed" : "open";
}
static invertDesignation(designation) {
return designation === "low" ? "high" : "low";
}
static closed(low, high) {
return new Interval(low, high, INTERVAL_CLOSED);
}
static open(low, high) {
return new Interval(low, high, INTERVAL_OPEN);
}
static closedOpen(low, high) {
return new Interval(low, high, INTERVAL_CLOSED_OPEN);
}
static openClosed(low, high) {
return new Interval(low, high, INTERVAL_OPEN_CLOSED);
}
static parse(span) {
const asVec = new Vec2(span);
return new Interval(asVec.x, asVec.y);
}
/**
* Shrinks or extends the interval on both ends
* @param n
*/
pad(n) {
const newLow = this.low - n;
const newHigh = this.high + n;
this.low = Math.min(newLow, newHigh);
this.high = Math.max(newLow, newHigh);
}
clampInto(n) {
if (this.isTooLow(n)) {
return this.lowest();
} else if (this.isTooHigh(n)) {
return this.highest();
} else {
return n;
}
}
isClosedInterval() {
return this.lowQualifier === INTERVAL_ENDPOINT_CLOSED_QUALIFIER && this.highQualifier === INTERVAL_ENDPOINT_CLOSED_QUALIFIER;
}
isOpenInterval() {
return this.lowQualifier === INTERVAL_ENDPOINT_OPEN_QUALIFIER && this.highQualifier === INTERVAL_ENDPOINT_OPEN_QUALIFIER;
}
isClosedOpenInterval() {
return this.lowQualifier === INTERVAL_ENDPOINT_CLOSED_QUALIFIER && this.highQualifier === INTERVAL_ENDPOINT_OPEN_QUALIFIER;
}
isOpenClosedInterval() {
return this.lowQualifier === INTERVAL_ENDPOINT_OPEN_QUALIFIER && this.highQualifier === INTERVAL_ENDPOINT_CLOSED_QUALIFIER;
}
get length() {
const bothClosedOffset = this.isClosedInterval() ? 1 : 0;
const bothOpenOffset = this.isOpenInterval() ? -1 : 0;
return Math.max(bothClosedOffset + bothOpenOffset + this.high - this.low, 0);
}
moveBy(by) {
if (Number.isFinite(this.low)) {
this.low += by;
}
if (Number.isFinite(this.high)) {
this.high += by;
}
return this;
}
moveLowTo(low) {
if (Number.isFinite(this.low)) {
const diff = this.low - low;
this.high = this.high - diff;
} else if (this.low === Number.NEGATIVE_INFINITY && low !== Number.NEGATIVE_INFINITY) {
this.high = Number.POSITIVE_INFINITY;
}
this.low = low;
return this;
}
moveHighTo(high) {
if (Number.isFinite(this.high)) {
const diff = this.high - high;
this.low = this.low - diff;
} else if (this.high === Number.POSITIVE_INFINITY && high !== Number.POSITIVE_INFINITY) {
this.low = Number.NEGATIVE_INFINITY;
}
this.high = high;
return this;
}
map(mapper) {
return this.reduce((acc, next) => {
acc.push(mapper(next));
return acc;
}, []);
}
reduce(reducer, initialValue) {
let accumulator = initialValue;
for (const item of this.iter()) {
accumulator = reducer(accumulator, item);
}
return accumulator;
}
merge(...others) {
return Interval.merge([this, ...others]);
}
mergeOne(other) {
const [lowestLow] = [this, other].sort(Interval.compareByLow);
const [, highestHigh] = [this, other].sort(Interval.compareByHigh);
return this.intersects(other) ? new Interval(lowestLow.low, highestHigh.high, {
lowQualifier: lowestLow.lowQualifier,
highQualifier: highestHigh.highQualifier
}) : void 0;
}
static complement(intervals, within) {
const points = Interval.collectAllSignificantPoints(intervals).map(
Interval.invertQualifiedNumber
);
if (within) {
points.push(...Interval.collectAllSignificantPoints(within));
}
return Interval.mergeQualifiedNumbers(points, true).filter((m) => !m.isEmpty());
}
/**
* An interval is empty if both its high and low values are the same, and
* it's not a closed interval
*/
static isEmpty(interval) {
return interval.low === interval.high && (interval.lowQualifier === INTERVAL_ENDPOINT_OPEN_QUALIFIER || interval.highQualifier === INTERVAL_ENDPOINT_OPEN_QUALIFIER);
}
isEmpty() {
return Interval.isEmpty(this);
}
/**
* Returns this interval trimmed into another, using the lowest high value
* and the highest low value.
*
* If the interval is completely enveloped, it is simply returned.
*
* If there is no intersection, undefined is returned.
*/
static trim(interval, within) {
if (interval.isAfterOf(within) || interval.isBeforeOf(within)) {
return void 0;
} else if (within.envelops(interval)) {
return interval;
} else {
let low = interval.low;
let lowQualifier = interval.lowQualifier;
if (interval.low < within.low) {
low = within.low;
lowQualifier = within.lowQualifier;
}
let high = interval.high;
let highQualifier = interval.highQualifier;
if (interval.high > within.high) {
high = within.high;
highQualifier = within.highQualifier;
}
return new Interval(low, high, { lowQualifier, highQualifier });
}
}
/**
* Returns this interval trimmed into another, using the lowest high value
* and the highest low value.
*
* If the interval is completely enveloped, it is simply returned.
*
* If there is no intersection, undefined is returned.
*/
trim(within) {
return Interval.trim(this, within);
}
static invertQualifiedNumber(qualifiedNumber) {
return {
value: qualifiedNumber.value,
originalDesignation: Interval.invertDesignation(qualifiedNumber.originalDesignation),
highQualifier: qualifiedNumber.highQualifier,
lowQualifier: qualifiedNumber.lowQualifier
};
}
static collectAllSignificantPoints(intervals) {
const result = [];
for (const interval of intervals) {
if (!interval.isEmpty()) {
result.push(
{
originalDesignation: "low",
value: interval.low,
lowQualifier: interval.lowQualifier,
highQualifier: Interval.invertQualifier(interval.lowQualifier)
},
{
originalDesignation: "high",
value: interval.high,
lowQualifier: Interval.invertQualifier(interval.highQualifier),
highQualifier: interval.highQualifier
}
);
}
}
return result.sort(Interval.compareQualifiedNumber);
}
static mergeQualifiedNumbers(qualifiedNumbers, useSort = true) {
const result = [];
if (useSort) {
qualifiedNumbers.sort(Interval.compareQualifiedNumber);
}
const intervalStartStack = [];
if (qualifiedNumbers[0]?.originalDesignation === "high") {
intervalStartStack.push({
value: Number.NEGATIVE_INFINITY,
lowQualifier: "open",
highQualifier: "closed",
originalDesignation: "low"
});
}
for (const qualifiedNumber of qualifiedNumbers) {
if (qualifiedNumber.originalDesignation === "low") {
intervalStartStack.push(qualifiedNumber);
} else if (qualifiedNumber.originalDesignation === "high" && intervalStartStack.length > 0) {
const matchingLow = intervalStartStack.shift();
const next = new Interval(matchingLow.value, qualifiedNumber.value, {
lowQualifier: matchingLow.lowQualifier,
highQualifier: qualifiedNumber.highQualifier
});
if (!next.isEmpty()) {
result.push(next);
}
}
}
const last = qualifiedNumbers.at(-1);
if (last?.originalDesignation === "low") {
result.push(
new Interval(last.value, Number.POSITIVE_INFINITY, {
lowQualifier: last.lowQualifier,
highQualifier: "open"
})
);
}
return Interval.merge(result);
}
static merge(intervals) {
const [first, ...remaining] = intervals.sort(Interval.compareByLow);
if (first) {
const result = [first];
for (const span of remaining) {
const mergeBase = result.pop();
const mergeResult = mergeBase.mergeOne(span);
if (mergeResult) {
result.push(mergeResult);
} else {
result.push(mergeBase, span);
}
}
return result;
} else {
return [];
}
}
/**
* Lowest possible integer number. This takes openness into account. low or low + 1 when open
*/
lowest() {
return this.lowQualifier === INTERVAL_ENDPOINT_CLOSED_QUALIFIER ? this.low : this.low + 1;
}
/**
* Highest possible integer number, this takes openness into account, high, or high - 1 when open
*/
highest() {
return this.highQualifier === INTERVAL_ENDPOINT_CLOSED_QUALIFIER ? this.high : this.high - 1;
}
/**
* Checks if a single value is above the high value
*/
static isTooHigh(interval, n) {
return interval.highQualifier === INTERVAL_ENDPOINT_CLOSED_QUALIFIER ? interval.high < n : interval.high <= n;
}
/**
* Checks if a single value is not above the high value
*/
static isBelowHigh(interval, n) {
return !Interval.isTooHigh(interval, n);
}
/**
* Checks if a single value is below the low value of the interval
*/
static isTooLow(interval, n) {
return interval.lowQualifier === INTERVAL_ENDPOINT_OPEN_QUALIFIER ? n <= interval.low : n < interval.low;
}
/**
* Checks if a single value is not below the low value of the interval
*/
static isAboveLow(interval, n) {
return !Interval.isTooLow(interval, n);
}
/**
* Checks if a single value is above the high value
*/
isTooHigh(n) {
return Interval.isTooHigh(this, n);
}
/**
* Checks if a single value is not above the high value
*/
isBelowHigh(n) {
return Interval.isBelowHigh(this, n);
}
/**
* Checks if a single value is below the low value of the interval
*/
isTooLow(n) {
return Interval.isTooLow(this, n);
}
/**
* Checks if a single value is not below the low value of the interval
*/
isAboveLow(n) {
return Interval.isAboveLow(this, n);
}
*iter() {
for (let i = this.lowest(); this.contains(i); i++) {
yield i;
}
}
collectValues() {
return [...this.iter()];
}
/**
* Checks if the first parameter is completely enveloped by the second
*/
static envelops(a, b) {
return Interval.isAboveLow(b, a.low) && Interval.isAboveLow(b, a.high) && Interval.isBelowHigh(b, a.low) && Interval.isBelowHigh(b, a.high);
}
/**
* Checks if this interal is completely enveloped by the one passed in
*/
envelops(other) {
return Interval.envelops(this, other);
}
/**
* Returns if the first parameter is completely above of the second parameter
*/
static isAfterOf(a, b) {
return Interval.isTooHigh(b, a.low) && Interval.isTooHigh(b, a.high);
}
/**
* Returns if the first parameter is completely above of the second parameter
*/
static isBeforeOf(a, b) {
return Interval.isTooLow(b, a.low) && Interval.isTooLow(b, a.high);
}
/**
* Checks if an interval is completely below another
*/
isBeforeOf(other) {
return Interval.isBeforeOf(this, other);
}
/**
* Checks if an interval is completely above another
*/
isAfterOf(other) {
return Interval.isAfterOf(this, other);
}
clone() {
return new Interval(this.low, this.high, {
lowQualifier: this.lowQualifier,
highQualifier: this.highQualifier
});
}
contains(n) {
return Interval.contains(this, n);
}
isFinite() {
return Number.isFinite(this.low) && Number.isFinite(this.high);
}
intersection(other) {
return Interval.intersection(this, other);
}
static intersection(a, b) {
if (!Interval.intersects(a, b)) {
return void 0;
} else if (b.isFinite() && Interval.contains(a, b.low) && Interval.contains(a, b.high)) {
return b;
} else if (a.isFinite() && Interval.contains(b, a.low) && Interval.contains(b, a.high)) {
return a;
} else {
const [, highestLow] = [a, b].sort(Interval.compareByLow);
const [lowestHigh] = [a, b].sort(Interval.compareByLow);
return new Interval(highestLow.low, lowestHigh.high, {
lowQualifier: highestLow.lowQualifier,
highQualifier: lowestHigh.highQualifier
});
}
}
static intersect(intersections) {
let result = intersections[0];
if (result === void 0) {
return void 0;
}
for (let i = 1; i <= intersections.length; i++) {
result = result?.intersection(intersections[i]);
}
return result;
}
/**
* Comparator, comparing only the low end of an interval. When they are
* equal, use the qualifier.
*
* For the low end, closed comes earlier
*/
static compareByLow(a, b) {
return a.low === b.low ? a.lowQualifier === INTERVAL_ENDPOINT_OPEN_QUALIFIER ? 1 : -1 : a.low - b.low;
}
/**
* a low designation comes before the high designation
*/
static compareEndpointDesignation(a, b) {
return a === b ? 0 : a === "low" ? -1 : 1;
}
/**
* Comparator, comparing qualified numbers
*
* For the low end, closed comes earlier
*/
static compareQualifiedNumber(a, b) {
return a.value === b.value ? Interval.compareEndpointDesignation(a.originalDesignation, b.originalDesignation) : a.value - b.value;
}
/**
* Comparator, comparing only the high end of an interval. When they are
* equal, use the qualifier.
*
* For the high end, open comes earlier
*/
static compareByHigh(a, b) {
return a.high === b.high ? a.highQualifier === INTERVAL_ENDPOINT_CLOSED_QUALIFIER ? 1 : -1 : a.high - b.high;
}
intersects(other) {
return Interval.intersects(this, other);
}
static intersects(a, b) {
return Interval.contains(a, b.low) || Interval.contains(a, b.high) || Interval.contains(b, a.low) || Interval.contains(b, a.high);
}
static contains(interval, n) {
return Interval.isAboveLow(interval, n) && Interval.isBelowHigh(interval, n);
}
static equals(a, b) {
return (a && b && a.low === b.low && a.high === b.high && (a.lowQualifier ?? "open") === (b.lowQualifier ?? "open") && (a.highQualifier ?? "closed") === (b.highQualifier ?? "closed")) ?? false;
}
equals(other) {
return Interval.equals(this, other);
}
/**
*
* @returns a copy of this interval with both endpoint qualifiers being closed
*/
asClosed() {
return new Interval(this.low, this.high, {
lowQualifier: INTERVAL_ENDPOINT_CLOSED_QUALIFIER,
highQualifier: INTERVAL_ENDPOINT_CLOSED_QUALIFIER
});
}
/**
*
* @returns a copy of this interval with the low endpoint qualifier being closed and the high open
*/
asClosedOpen() {
return new Interval(this.low, this.high, {
lowQualifier: INTERVAL_ENDPOINT_CLOSED_QUALIFIER,
highQualifier: INTERVAL_ENDPOINT_OPEN_QUALIFIER
});
}
/**
*
* @returns a copy of this interval with both endpoint qualifiers being open
*/
asOpen() {
return new Interval(this.low, this.high, {
lowQualifier: INTERVAL_ENDPOINT_OPEN_QUALIFIER,
highQualifier: INTERVAL_ENDPOINT_OPEN_QUALIFIER
});
}
/**
*
* @returns a copy of this interval with the low endpoint qualifier being open and the high closed
*/
asOpenClosed() {
return new Interval(this.low, this.high, {
lowQualifier: INTERVAL_ENDPOINT_OPEN_QUALIFIER,
highQualifier: INTERVAL_ENDPOINT_CLOSED_QUALIFIER
});
}
closestEndTo(to) {
const ld = Math.abs(to - this.low);
const hd = Math.abs(this.high - to);
return ld < hd ? this.low : this.high;
}
toString() {
return `${this.lowQualifier === INTERVAL_ENDPOINT_OPEN_QUALIFIER ? "(" : "["}${this.low},${this.high}${this.highQualifier === INTERVAL_ENDPOINT_OPEN_QUALIFIER ? ")" : "]"}`;
}
}
const lerp1D = (from, to, options) => {
const lower = Math.min(from, to);
const higher = Math.max(from, to);
const start = options?.excludeStart ? lower + 1 : lower;
const end = options?.excludeEnd ? higher - 1 : higher;
if (start > end) {
return [];
}
const result = [];
for (let i = start; i <= end; i++) {
result.push(i);
}
return result;
};
const invMod = (a, m) => {
return Number(invModBigInt(BigInt(a), BigInt(m)));
};
const invModBigInt = (a, m) => {
const b0 = m;
let x0 = 0n;
let x1 = 1n;
let q;
let tmp;
if (m == 1n) {
return 1n;
}
while (a > 1n) {
if (m === 0n) {
throw new Error("Multiplicative inverse does not exist, tried to divide by 0");
}
q = a / m;
tmp = a;
a = m;
m = tmp % m;
tmp = x0;
x0 = x1 - q * x0;
x1 = tmp;
}
if (x1 < 0n) {
x1 = x1 + b0;
}
return x1;
};
const invModEgdc = (a, m) => {
return Number(invModEgdcBigInt(BigInt(a), BigInt(m)));
};
const invModEgdcBigInt = (a, m) => {
const [g, x] = egcdBigInt(a, m);
if (g === 1n) {
return posModBigInt(x, m);
} else {
throw new Error(`Modular inverse of ${a} modulo ${m} does not exist`);
}
};
const modExp = (a, b, n) => Number(modExpBigInt(BigInt(a), BigInt(b), BigInt(n)));
const modExpBigInt = (a, b, n) => {
a = a % n;
let result = 1n;
let x = a;
while (b > 0) {
const leastSignificantBit = b % 2n;
b = b >> 1n;
if (leastSignificantBit == 1n) {
result = result * x % n;
}
x = x * x % n;
}
return result;
};
const hasToString = (t) => typeof t.toString === "function";
var DirectionArrowUnicodeSymbol = /* @__PURE__ */ ((DirectionArrowUnicodeSymbol2) => {
DirectionArrowUnicodeSymbol2["NORTH"] = "↑";
DirectionArrowUnicodeSymbol2["EAST"] = "→";
DirectionArrowUnicodeSymbol2["SOUTH"] = "↓";
DirectionArrowUnicodeSymbol2["WEST"] = "←";
return DirectionArrowUnicodeSymbol2;
})(DirectionArrowUnicodeSymbol || {});
const isHorizontalDirectionArrowUnicodeSymbol = (symbol) => symbol === "→" || symbol === "←";
const isVericalDirectionArrowUnicodeSymbol = (symbol) => symbol === "↑" || symbol === "↓";
const isDirectionArrowUnicodeSymbol = (symbol) => isHorizontalDirectionArrowUnicodeSymbol(symbol) || isVericalDirectionArrowUnicodeSymbol(symbol);
var DirectionCardinalNumericClockwiseIndex = /* @__PURE__ */ ((DirectionCardinalNumericClockwiseIndex2) => {
DirectionCardinalNumericClockwiseIndex2[DirectionCardinalNumericClockwiseIndex2["NORTH"] = 0] = "NORTH";
DirectionCardinalNumericClockwiseIndex2[DirectionCardinalNumericClockwiseIndex2["EAST"] = 1] = "EAST";
DirectionCardinalNumericClockwiseIndex2[DirectionCardinalNumericClockwiseIndex2["SOUTH"] = 2] = "SOUTH";
DirectionCardinalNumericClockwiseIndex2[DirectionCardinalNumericClockwiseIndex2["WEST"] = 3] = "WEST";
return DirectionCardinalNumericClockwiseIndex2;
})(DirectionCardinalNumericClockwiseIndex || {});
const isHorizontalDirectionCardinalNumericClockwiseIndex = (index) => index === 1 || index === 3;
const isVericalDirectionCardinalNumericClockwiseIndex = (index) => index === 0 || index === 2;
const isDirectionCardinalNumericClockwiseIndex = (index) => isHorizontalDirectionCardinalNumericClockwiseIndex(index) || isVericalDirectionCardinalNumericClockwiseIndex(index);
class Direction extends Vec2 {
static SYSTEM = "Y-DOWN";
constructor(x, y) {
super(x, y);
}
/**
* Returns the cardinal index of this direction clockwise
* NORTH: 0
* EAST: 1
* SOUTH: 2
* WEST: 3
*/
get cardinalValue() {
if (this.equals(Direction.NORTH)) {
return DirectionCardinalNumericClockwiseIndex.NORTH;
} else if (this.equals(Direction.EAST)) {
return DirectionCardinalNumericClockwiseIndex.EAST;
} else if (this.equals(Direction.SOUTH)) {
return DirectionCardinalNumericClockwiseIndex.SOUTH;
} else if (this.equals(Direction.WEST)) {
return DirectionCardinalNumericClockwiseIndex.WEST;
} else {
return void 0;
}
}
get reverseValue() {
return Direction.reverseValue(this.cardinalValue);
}
static ZERO = Object.freeze(new Direction(0, 0));
static EAST = Object.freeze(new Direction(1, 0));
static NORTHEAST = Object.freeze(new Direction(1, -1));
static NORTH = Object.freeze(new Direction(0, -1));
static NORTHWEST = Object.freeze(new Direction(-1, -1));
static WEST = Object.freeze(new Direction(-1, 0));
static SOUTHWEST = Object.freeze(new Direction(-1, 1));
static SOUTH = Object.freeze(new Direction(0, 1));
static SOUTHEAST = Object.freeze(new Direction(1, 1));
/**
* Main directions
* N, W, S, E
*
* Counter-Clockwise from east
*/
static cardinalDirections = Object.freeze([
Direction.NORTH,
Direction.WEST,
Direction.SOUTH,
Direction.EAST
]);
/**
* Diagonal directions (Intercardinal)
* NE, SE, SW, NW
*
* Clockwise from north
*/
static ordinalDirections = Object.freeze([
Direction.NORTHEAST,
Direction.SOUTHEAST,
Direction.SOUTHWEST,
Direction.NORTHWEST
]);
/**
* All 8 directions, cardinal and ordinal combined
* E, NE, N, NW, W, SW, S, SE
*
* Counter-Clockwise from east
*/
static allDirections = Object.freeze([
Direction.EAST,
Direction.NORTHEAST,
Direction.NORTH,
Direction.NORTHWEST,
Direction.WEST,
Direction.SOUTHWEST,
Direction.SOUTH,
Direction.SOUTHEAST
]);
static isHorizonal(marker) {
return marker === DirectionArrowSymbol.EAST || marker === DirectionArrowSymbol.WEST || marker === DirectionCardinalLiteralLetter.EAST || marker === DirectionCardinalLiteralLetter.WEST || marker === DirectionCardinalGeographicLetter.EAST || marker === DirectionCardinalGeographicLetter.WEST;
}
static isVertical(marker) {
return marker === DirectionArrowSymbol.NORTH || marker === DirectionArrowSymbol.SOUTH || marker === DirectionCardinalLiteralLetter.NORTH || marker === DirectionCardinalLiteralLetter.SOUTH || marker === DirectionCardinalGeographicLetter.NORTH || marker === DirectionCardinalGeographicLetter.SOUTH;
}
sameAxis(other) {
return this.isHorizonal() ? other.isHorizonal() : other.isVertical();
}
static fromMarker(marker) {
return directionMarkerAssociationMap[marker];
}
static reverseValue(v) {
switch (v) {
case DirectionCardinalNumericClockwiseIndex.NORTH: {
return DirectionCardinalNumericClockwiseIndex.SOUTH;
}
case DirectionCardinalNumericClockwiseIndex.EAST: {
return DirectionCardinalNumericClockwiseIndex.WEST;
}
case DirectionCardinalNumericClockwiseIndex.SOUTH: {
return DirectionCardinalNumericClockwiseIndex.NORTH;
}
case DirectionCardinalNumericClockwiseIndex.WEST: {
return DirectionCardinalNumericClockwiseIndex.EAST;
}
default: {
return void 0;
}
}
}
isHorizonal() {
return this.equals(Direction.WEST) || this.equals(Direction.EAST);
}
isVertical() {
return this.equals(Direction.NORTH) || this.equals(Direction.SOUTH);
}
/**
* @param angle must be a multiple of 45, turns clockwise
*/
turn(angle) {
const step = (this.angularValue + angle % 360) / 45;
const direction = Direction.allDirections[(step + Direction.allDirections.length) % Direction.allDirections.length];
if (direction) {
return direction;
} else {
throw new Error(`Not a valid angle: ${angle}, it must be a multiple of 45!`);
}
}
/**
* Returns angles from [0, 1] (east)
*
* Counter-Clockwise
*/
get angularValue() {
if (this.equals(Direction.EAST))
return 0;
else if (this.equals(Direction.NORTHEAST))
return 45;
else if (this.equals(Direction.NORTH))
return 90;
else if (this.equals(Direction.NORTHWEST))
return 135;
else if (this.equals(Direction.WEST))
return 180;
else if (this.equals(Direction.SOUTHWEST))
return 225;
else if (this.equals(Direction.SOUTH))
return 270;
else if (this.equals(Direction.SOUTHEAST))
return 315;
else
return 0;
}
/**
* @param angle must be a multiple of 45
*/
right(angle = 90) {
return this.turn(-angle);
}
/**
* @param angle must be a multiple of 45
*/
left(angle = 90) {
return this.turn(angle);
}
reverse(axis) {
if ((!axis || axis === "v") && this.equals(Direction.NORTH))
return Direction.SOUTH;
else if ((!axis || axis === "h") && this.equals(Direction.WEST))
return Direction.EAST;
else if ((!axis || axis === "v") && this.equals(Direction.SOUTH))
return Direction.NORTH;
else if ((!axis || axis === "h") && this.equals(Direction.EAST))
return Direction.WEST;
else
return this;
}
equals(that) {
return this.x === that.x && this.y === that.y;
}
clone() {
return new Direction(this);
}
get marker() {
if (this.equals(Direction.EAST))
return DirectionArrowSymbol.EAST;
else if (this.equals(Direction.NORTH))
return DirectionArrowSymbol.NORTH;
else if (this.equals(Direction.WEST))
return DirectionArrowSymbol.WEST;
else if (this.equals(Direction.SOUTH))
return DirectionArrowSymbol.SOUTH;
else
return " ";
}
static getNameOf(direction) {
return Object.entries(directionNameMap).find(([, d]) => direction.equals(d))?.[0] ?? "";
}
}
const directionNameMap = {
[DirectionNames.NORTH]: Direction.NORTH,
[DirectionNames.EAST]: Direction.EAST,
[DirectionNames.SOUTH]: Direction.SOUTH,
[DirectionNames.WEST]: Direction.WEST
};
const directionMarkerAssociationMap = {
[DirectionArrowSymbol.NORTH]: Direction.NORTH,
[DirectionCardinalGeographicLetter.NORTH]: Direction.NORTH,
[DirectionCardinalLiteralLetter.NORTH]: Direction.NORTH,
[DirectionArrowSymbol.EAST]: Direction.EAST,
[DirectionCardinalGeographicLetter.EAST]: Direction.EAST,
[DirectionCardinalLiteralLetter.EAST]: Direction.EAST,
[DirectionArrowSymbol.SOUTH]: Direction.SOUTH,
[DirectionCardinalGeographicLetter.SOUTH]: Direction.SOUTH,
[DirectionCardinalLiteralLetter.SOUTH]: Direction.SOUTH,
[DirectionArrowSymbol.WEST]: Direction.WEST,
[DirectionCardinalGeographicLetter.WEST]: Direction.WEST,
[DirectionCardinalLiteralLetter.WEST]: Direction.WEST,
"": Direction.ZERO
};
const directionMarkerInvertMap = {
[DirectionArrowSymbol.NORTH]: DirectionArrowSymbol.SOUTH,
[DirectionCardinalGeographicLetter.NORTH]: DirectionCardinalGeographicLetter.SOUTH,
[DirectionCardinalLiteralLetter.NORTH]: DirectionCardinalLiteralLetter.SOUTH,
[DirectionArrowSymbol.EAST]: DirectionArrowSymbol.WEST,
[DirectionCardinalGeographicLetter.EAST]: DirectionCardinalGeographicLetter.WEST,
[DirectionCardinalLiteralLetter.EAST]: DirectionCardinalLiteralLetter.WEST,
[DirectionArrowSymbol.SOUTH]: DirectionArrowSymbol.NORTH,
[DirectionCardinalGeographicLetter.SOUTH]: DirectionCardinalGeographicLetter.NORTH,
[DirectionCardinalLiteralLetter.SOUTH]: DirectionCardinalLiteralLetter.NORTH,
[DirectionArrowSymbol.WEST]: DirectionArrowSymbol.EAST,
[DirectionCardinalGeographicLetter.WEST]: DirectionCardinalGeographicLetter.EAST,
[DirectionCardinalLiteralLetter.WEST]: DirectionCardinalLiteralLetter.EAST,
"": ""
};
const constructPath = (start, end, prevMap) => {
const path = [];
if (end) {
let u = end;
if (start === u || prevMap.get(u)) {
while (u) {
path.unshift(u);
u = prevMap.get(u);
}
}
}
return path;
};
const calculateCostOfTraversal = (edge, currentPathWeighter, pathConstructor) => {
let cost = 1;
if (isNotNullish(currentPathWeighter) && isNotNullish(pathConstructor)) {
cost = currentPathWeighter(edge.from, edge.to, edge.direction, pathConstructor(edge.to));
} else if (isNotNullish(edge.weight)) {
cost = edge.weight;
}
return cost;
};
const collectEdges = (node, options) => {
const edgeGenerator = options?.edgeGenerator;
let edges = edgeGenerator ? edgeGenerator(options.allNodes, node, options.pathConstructor(node)) : [...node.neighbours.values()];
const edgeFilter = options?.edgeFilter;
if (edgeFilter) {
edges = edges.filter((edge) => edgeFilter(edge, options.pathConstructor(node)));
}
return edges;
};
const dijkstra = (options) => {
const dist = /* @__PURE__ */ new Map();
const pathLengthMap = /* @__PURE__ */ new Map();
const prev = /* @__PURE__ */ new Map();
const pq = new PriorityQueue(options.allNodes, (a, b) => {
const aDist = dist.get(a) ?? Number.POSITIVE_INFINITY;
const bDist = dist.get(b) ?? Number.POSITIVE_INFINITY;
return aDist - bDist;
});
const pathConstructor = (to) => constructPath(options.start, to, prev);
const isFinished = isNotNullish(options.end) ? (n) => typeof options.end === "function" ? options.end(n, pathConstructor(n)) : n === options.end : (_n) => false;
dist.set(options.start, 0);
pathLengthMap.set(options.start, 0);
pq.updateItem(options.start);
let target;
while (!pq.empty()) {
const u = pq.pop();
if (isFinished(u)) {
target = u;
break;
}
const uDist = dist.get(u) ?? Number.POSITIVE_INFINITY;
for (const neighbour of collectEdges(u, {
pathConstructor,
allNodes: options.allNodes,
edgeFilter: options.edgeFilter,
edgeGenerator: options.edgeGenerator
})) {
const weight = calculateCostOfTraversal(
neighbour,
options.currentPathWeighter,
pathConstructor
);
const tentativegScore = uDist + weight;
const currentCost = dist.get(neighbour.to) ?? Number.POSITIVE_INFINITY;
if (tentativegScore < currentCost) {
dist.set(neighbour.to, tentativegScore);
prev.set(neighbour.to, u);
pathLengthMap.set(neighbour.to, (pathLengthMap.get(u) ?? 0) + 1);
pq.updateItem(neighbour.to);
}
}
}
return {
distances: pathLengthMap,
path: target ? constructPath(options.start, target, prev) : []
};
};
const aStar = (options) => {
if (!options.start) {
return { path: [], distances: /* @__PURE__ */ new Map() };
}
const h = options?.heuristic ?? (() => 1);
const prev = /* @__PURE__ */ new Map();
const gScore = /* @__PURE__ */ new Map();
const fScore = /* @__PURE__ */ new Map();
const pathLengthMap = /* @__PURE__ */ new Map();
gScore.set(options.start, 0);
fScore.set(options.start, options.end ? h(options.start, []) : 1);
pathLengthMap.set(options.start, 0);
const orderOfDiscovery = [options.start];
const pq = new PriorityQueue([options.start], (a, b) => {
const aScore = fScore.get(a) ?? Number.POSITIVE_INFINITY;
const bScore = fScore.get(b) ?? Number.POSITIVE_INFINITY;
if (aScore === bScore) {
let aPathLength = orderOfDiscovery.indexOf(a);
let bPathLength = orderOfDiscovery.indexOf(b);
if (aPathLength < 0) {
aPathLength = Number.POSITIVE_INFINITY;
}
if (bPathLength < 0) {
bPathLength = Number.POSITIVE_INFINITY;
}
return aPathLength - bPathLength;
} else {
return aScore - bScore;
}
});
const pathConstructor = (to) => constructPath(options.start, to, prev);
const isFinished = isNotNullish(options.end) ? (n) => typeof options.end === "function" ? options.end(n, pathConstructor(n)) : n === options.end : (_n) => false;
let goal;
while (pq.length > 0) {
const current = pq.pop();
orderOfDiscovery.removeItem(current);
if (isFinished(current)) {
goal = current;
break;
}
const uDist = gScore.get(current) ?? Number.POSITIVE_INFINITY;
for (const neighbour of collectEdges(current, {
pathConstructor,
allNodes: options.allNodes,
edgeFilter: options.edgeFilter,
edgeGenerator: options.edgeGenerator
})) {
const weight = calculateCostOfTravers