UNPKG

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
/** * @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; }