@thi.ng/intervals
Version:
Closed/open/semi-open interval data type, queries & operations
200 lines (199 loc) • 6.99 kB
JavaScript
import { DEFAULT_EPS } from "@thi.ng/api/api";
import { isString } from "@thi.ng/checks/is-string";
import { and, or } from "@thi.ng/dlogic";
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
var Classifier = /* @__PURE__ */ ((Classifier2) => {
Classifier2[Classifier2["DISJOINT_LEFT"] = 0] = "DISJOINT_LEFT";
Classifier2[Classifier2["DISJOINT_RIGHT"] = 1] = "DISJOINT_RIGHT";
Classifier2[Classifier2["EQUIV"] = 2] = "EQUIV";
Classifier2[Classifier2["SUBSET"] = 3] = "SUBSET";
Classifier2[Classifier2["SUPERSET"] = 4] = "SUPERSET";
Classifier2[Classifier2["OVERLAP_LEFT"] = 5] = "OVERLAP_LEFT";
Classifier2[Classifier2["OVERLAP_RIGHT"] = 6] = "OVERLAP_RIGHT";
return Classifier2;
})(Classifier || {});
class Interval {
l;
r;
lopen;
ropen;
constructor(l, r, lopen = false, ropen = false) {
(l > r || l === r && lopen !== ropen) && illegalArgs(`invalid interval: ${$toString(l, r, lopen, ropen)}`);
this.l = l;
this.r = r;
this.lopen = lopen;
this.ropen = ropen;
}
get size() {
return isEmpty(this) ? 0 : this.r - this.l;
}
copy() {
return new Interval(this.l, this.r, this.lopen, this.ropen);
}
/**
* Compares this interval with `i` and returns a comparator value
* (-1, 0 or 1). Comparison order is: LHS, RHS, openness.
*
* @param i -
*/
compare(i) {
return compare(this, i);
}
equiv(i) {
return this === i || i instanceof Interval && this.l === i.l && this.r === i.r && this.lopen === i.lopen && this.ropen === i.ropen;
}
/**
* Returns true if `x` is lies within this interval.
*
* @param x -
*/
contains(x) {
return contains(this, x);
}
toString() {
return $toString(this.l, this.r, this.lopen, this.ropen);
}
toJSON() {
return this.toString();
}
}
const BRACES = "()[]";
const RE_INF = /^([-+])?(inf(inity)?|\u221e)$/i;
function interval(l, r, lopen, ropen) {
return isString(l) ? parse(l) : new Interval(l, r, lopen, ropen);
}
const parse = (src) => {
let l, r, c1, c2;
const n = src.length - 1;
const comma = src.indexOf(",") > 0;
const dot = src.indexOf("..") > 0;
if (n < (dot ? 3 : 2)) illegalArgs(src);
c1 = src.charAt(0);
c2 = src.charAt(n);
if (BRACES.indexOf(c1) < 0 || BRACES.indexOf(c2) < 0 || !(comma || dot)) {
illegalArgs(src);
}
[l, r] = src.substring(1, n).split(dot ? ".." : ",").map((x, i) => {
x = x.trim();
const inf = RE_INF.exec(x);
const n2 = x === "" && i === 0 || inf && inf[1] === "-" ? -Infinity : x === "" && i > 0 || inf && inf[1] !== "-" ? Infinity : parseFloat(x);
isNaN(n2) && illegalArgs(`expected number: '${x}'`);
return n2;
});
r === void 0 && (r = Infinity);
return new Interval(
l,
r,
c1 === "(" || c1 === "]",
c2 === ")" || c2 === "["
);
};
const infinity = () => new Interval(-Infinity, Infinity);
const withMin = (min2, open2 = false) => new Interval(min2, Infinity, open2, false);
const withMax = (max2, open2 = false) => new Interval(-Infinity, max2, false, open2);
const open = (min2, max2) => new Interval(min2, max2, true, true);
const openClosed = (min2, max2) => new Interval(min2, max2, true, false);
const closedOpen = (min2, max2) => new Interval(min2, max2, false, true);
const closed = (min2, max2) => new Interval(min2, max2, false, false);
const values = (i, step) => samples(i, Math.floor(i.size / step + 1));
function* samples(i, n) {
const delta = n > 1 ? (i.r - i.l) / (n - 1) : 0;
for (let x = 0; x < n; x++) {
const y = i.l + delta * x;
if (contains(i, y)) yield y;
}
}
const isEmpty = (i) => i.l >= i.r;
const isBefore = (i, x) => x instanceof Interval ? i.ropen || x.lopen ? i.r <= x.l : i.r < x.l : i.ropen ? i.r <= x : i.r < x;
const isAfter = (i, x) => x instanceof Interval ? i.lopen || x.ropen ? i.l >= x.r : i.l > x.r : i.ropen ? i.l >= x : i.l > x;
const compare = (a, b) => {
if (a === b) return 0;
let c;
return a.l < b.l ? -1 : a.l > b.l ? 1 : a.r < b.r ? -1 : a.r > b.r ? 1 : (c = ~~a.lopen - ~~b.lopen) === 0 ? ~~b.ropen - ~~a.ropen : c;
};
const contains = (i, x) => (i.lopen ? x > i.l : x >= i.l) && (i.ropen ? x < i.r : x <= i.r);
const centroid = (i) => (i.l + i.r) / 2;
const include = (i, x) => isAfter(i, x) ? new Interval(x, i.r, false, i.ropen) : isBefore(i, x) ? new Interval(i.l, x, i.lopen, false) : i.copy();
const distance = (a, b) => overlaps(a, b) ? 0 : a.l < b.l ? b.l - a.r : a.l - b.r;
const transform = (i, fn) => new Interval(fn(i.l), fn(i.r), i.lopen, i.ropen);
const classify = (a, b) => a.equiv(b) ? 2 /* EQUIV */ : isBefore(a, b) ? 0 /* DISJOINT_LEFT */ : isAfter(a, b) ? 1 /* DISJOINT_RIGHT */ : contains(a, b.l) ? contains(a, b.r) ? 4 /* SUPERSET */ : 6 /* OVERLAP_RIGHT */ : contains(a, b.r) ? 5 /* OVERLAP_LEFT */ : 3 /* SUBSET */;
const overlaps = (a, b) => classify(a, b) >= 2 /* EQUIV */;
const union = (a, b) => {
if (isEmpty(a)) return b;
if (isEmpty(b)) return a;
const [l, lo] = $min(a.l, b.l, a.lopen, b.lopen, and);
const [r, ro] = $max(a.r, b.r, a.ropen, b.ropen, and);
return new Interval(l, r, lo, ro);
};
const intersection = (a, b) => {
if (overlaps(a, b)) {
const [l, lo] = $max(a.l, b.l, a.lopen, b.lopen, or);
const [r, ro] = $min(a.r, b.r, a.ropen, b.ropen, or);
return new Interval(l, r, lo, ro);
}
};
const prefix = (a, b) => {
if (overlaps(a, b)) {
const [l, lo] = $min(a.l, b.l, a.lopen, b.lopen, or);
const [r, ro] = $min(a.r, b.l, a.ropen, b.lopen, or);
return new Interval(l, r, lo, ro);
}
};
const suffix = (a, b) => {
if (overlaps(a, b)) {
const [l, lo] = $max(a.l, b.r, a.lopen, b.ropen, or);
const [r, ro] = $max(a.r, b.r, a.ropen, b.ropen, or);
return new Interval(l, r, lo, ro);
}
};
const min = (i, x, eps = DEFAULT_EPS) => i.ropen ? x >= i.r ? i.r - eps : x : x > i.r ? i.r : x;
const max = (i, x, eps = DEFAULT_EPS) => i.lopen ? x <= i.l ? i.l + eps : x : x < i.l ? i.l : x;
const clamp = (i, x, eps = DEFAULT_EPS) => min(i, max(i, x, eps), eps);
const fold = (i, x, eps = DEFAULT_EPS) => {
do {
if (i.lopen && x <= i.l || !i.lopen && x < i.l) {
x = x - i.l + i.r - (i.ropen ? eps : 0);
} else if (i.ropen && x >= i.r || !i.ropen && x > i.r) {
x = x - i.r + i.l + (i.lopen ? eps : 0);
}
} while (!contains(i, x));
return x;
};
const $min = (a, b, ao, bo, op) => $minmax(a < b, a, b, ao, bo, op);
const $max = (a, b, ao, bo, op) => $minmax(a > b, a, b, ao, bo, op);
const $minmax = (test, a, b, ao, bo, op) => test ? [a, ao] : a === b ? [a, op(ao, bo)] : [b, bo];
const $toString = (l, r, lopen, ropen) => `${lopen ? "(" : "["}${l} .. ${r}${ropen ? ")" : "]"}`;
export {
Classifier,
Interval,
centroid,
clamp,
classify,
closed,
closedOpen,
compare,
contains,
distance,
fold,
include,
infinity,
intersection,
interval,
isAfter,
isBefore,
isEmpty,
max,
min,
open,
openClosed,
overlaps,
parse,
prefix,
samples,
suffix,
transform,
union,
values,
withMax,
withMin
};