maxintervalcover
Version:
The RAW MaxIntervalCover library computes the optimal subset of non-overlapping intervals that maximizes total covered length
107 lines (87 loc) • 2.98 kB
JavaScript
/**
* @license MaxIntervalCover.js v0.0.1 9/5/2025
* https://github.com/rawify/MaxIntervalCover
*
* Copyright (c) 2025, Robert Eisele (https://raw.org/)
* Licensed under the MIT license.
**/
// Maximize total covered length; on ties, prefer fewer intervals
// Intervals: half-open [a, b) by default, closed [a, b] if isHalfOpen=false
function MaxIntervalCover(ints, isHalfOpen = true) {
const norm = [];
for (let i = 0; i < ints.length; i++) {
const it = ints[i];
let a, b;
// accept both [a,b] arrays and {a,b} objects
if (Array.isArray(it) && it.length >= 2) {
[a, b] = it;
} else if (it && typeof it === "object" && "a" in it && "b" in it) {
({ a, b } = it);
} else {
continue; // invalid entry
}
if (!(Number.isFinite(a) && Number.isFinite(b))) continue;
if (b <= a) continue; // drop zero/negative length
norm.push({ "a": a, "b": b, "idx": i, "weight": b - a });
}
if (norm.length <= 1) return norm;
// Sort by end, then start
norm.sort((it1, it2) => (it1['b'] - it2['b']) || (it1['a'] - it2['a']));
// Find rightmost non-overlapping predecessor
function predIndex(i) {
let lo = 0;
let hi = i - 1;
let res = -1;
const a = norm[i]['a'];
while (lo <= hi) {
const mid = (lo + hi) >> 1;
const end = norm[mid]['b'];
if (end < a || (isHalfOpen && end === a)) {
res = mid;
lo = mid + 1;
} else {
hi = mid - 1;
}
}
return res;
}
const n = norm.length;
const p = new Int32Array(n);
for (let i = 0; i < n; i++)
p[i] = predIndex(i);
// DP arrays:
// bestSum[i] = best covered length using intervals up to i
// bestCnt[i] = corresponding minimal count for that bestSum
const bestSum = new Uint32Array(n);
const bestCnt = new Uint32Array(n);
const take = new Uint8Array(n);
for (let i = 0; i < n; i++) {
const pi = p[i];
const inclSum = norm[i]['weight'] + (pi >= 0 ? bestSum[pi] : 0);
const inclCnt = 1 + (pi >= 0 ? bestCnt[pi] : 0);
const exclSum = i > 0 ? bestSum[i - 1] : 0;
const exclCnt = i > 0 ? bestCnt[i - 1] : 0;
// Choose better: larger sum; on tie, fewer intervals
if (inclSum > exclSum || (inclSum === exclSum && inclCnt < exclCnt)) {
bestSum[i] = inclSum;
bestCnt[i] = inclCnt;
take[i] = 1;
} else {
bestSum[i] = exclSum;
bestCnt[i] = exclCnt;
take[i] = 0;
}
}
// Reconstruct chosen set
const chosen = [];
for (let i = n - 1; i >= 0;) {
if (take[i] === 1) {
chosen.push(ints[norm[i]['idx']]);
i = p[i];
} else {
i--;
}
}
chosen.reverse();
return chosen;
}