UNPKG

static-interval-tree

Version:

interval tree, for fast overlapping interval queries

116 lines (101 loc) 2.71 kB
// 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 */ 'use strict'; // 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); } };