static-interval-tree
Version:
interval tree, for fast overlapping interval queries
116 lines (101 loc) • 2.71 kB
JavaScript
// A simple static interval tree: static in the sense that
// we only do bulk loading, no add/remove after initial load.
//
// Because we don't support add/remove, we don't need a binary tree
// implementation that retains balance during add/remove (avl, red-black). Instead we
// can sort, then build a tree from the sort.
/*global module: false */
;
// Build a balanced binary tree from an ordered array.
function _toTree(arr, low, high) {
if (low >= high) {
return undefined;
}
var mid = Math.floor((high + low) / 2);
return {
el: arr[mid],
right: _toTree(arr, mid + 1, high),
left: _toTree(arr, low, mid)
};
}
var getHigh = function getHigh() {
var _ref = arguments.length <= 0 || arguments[0] === undefined ? { high: -Infinity } : arguments[0];
var high = _ref.high;
return high;
};
// Find the highest end value of each node. Mutates its input.
function findEnd(node) {
if (!node) {
return undefined;
}
var left = node.left;
var right = node.right;
var el = node.el;
findEnd(left);
findEnd(right);
node.high = Math.max(getHigh(left), getHigh(right), el.end);
return node;
}
var cmp = function cmp(x, y) {
return x === y ? 0 : x < y ? -1 : 1;
};
// Build index.
// intervals :: [{start, end, ...}, ...]
function index(intervals) {
var sorted = intervals.slice(0).sort(function (a, b) {
return cmp(a.start, b.start);
});
return findEnd(_toTree(sorted, 0, sorted.length));
}
function matchesAcc(node, pos, acc) {
if (node) {
var start = pos.start;
var end = pos.end;
if (node.high >= start) {
if (end >= node.el.start) {
if (node.el.end >= start) {
acc.push(node.el);
}
matchesAcc(node.right, pos, acc);
}
matchesAcc(node.left, pos, acc);
}
}
return acc;
}
// Find intervals in node overlapping position.
// pos :: {start, end}
var matches = function matches(node, pos) {
return matchesAcc(node, pos, []);
};
function matches01Acc(node, pos, acc) {
if (node) {
var start = pos.start;
var end = pos.end;
if (node.high > start) {
if (end > node.el.start) {
if (node.el.end > start) {
acc.push(node.el);
}
matches01Acc(node.right, pos, acc);
}
matches01Acc(node.left, pos, acc);
}
}
return acc;
}
// Find intervals in node overlapping position, using half-open coords.
// We could also support this by parameterizing the compare fn, though that adds
// more function calls to what is expected to be a hot loop.
// pos :: {start, end}
var matches01 = function matches01(node, pos) {
return matches01Acc(node, pos, []);
};
module.exports = {
matches: matches,
matches01: matches01,
index: index,
toTree: function toTree(arr) {
return _toTree(arr, 0, arr.length);
}
};