UNPKG

@thi.ng/intervals

Version:

Closed/open/semi-open interval data type, queries & operations

200 lines (199 loc) 6.99 kB
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 };