UNPKG

@flatten-js/interval-tree

Version:
258 lines (204 loc) 9.28 kB
# Interval Tree [![npm version](https://badge.fury.io/js/%40flatten-js%2Finterval-tree.svg)](https://badge.fury.io/js/%40flatten-js%2Finterval-tree) [![@flatten-js/interval-tree](https://snyk.io/advisor/npm-package/@flatten-js/interval-tree/badge.svg)](https://snyk.io/advisor/npm-package/@flatten-js/interval-tree) The package **@flatten-js/interval-tree** is an implementation of interval binary search tree according to Cormen et al. Introduction to Algorithms (2009, Section 14.3: Interval trees, pp. 348354). Cormen shows that insertion, deletion of nodes and range queries take *O(log(n))* time where n is the number of items stored in the tree. This package is a part of [flatten-js](https://github.com/alexbol99/flatten-js) library. An earlier implementation, [flatten-interval-tree](https://www.npmjs.com/package/flatten-interval-tree), is deprecated. Please use this package ([@flatten-js/interval-tree](https://www.npmjs.com/package/@flatten-js/interval-tree)) instead. ## Contacts LinkedIn: [Alexander Bol](https://www.linkedin.com/in/alexanderbol/) X (Twitter) [@alex_bol_](https://twitter.com/alex_bol_) ## Installation ```bash npm install --save @flatten-js/interval-tree ``` ## Usage ```ts import IntervalTree from '@flatten-js/interval-tree' // TypeScript: specify value type for better hints const tree = new IntervalTree<string>(); ``` ### Notes Tree stores pairs `<key, value>` where key is an interval, and value is an object of any type. If value is omitted, the tree stores the key itself as the value for convenience, so searches and iteration still return the key (you can always provide your own mapper). Keys with the same interval are now bucketed into a single node. This means multiple values can be stored under one identical key without requiring values to be comparable. No custom comparator is required for values. Equality for values is by strict equality (`===`) unless your value implements an `equal_to(other)` method. **Interval** can be a pair of numbers `[low, high]` (numeric pairs are normalized so that low <= high). User may also implement their own Interval class as an extension of the **IntervalBase** class. See Interval2d class in Interval.ts for an example of such extension. ### Example ```ts import IntervalTree from '@flatten-js/interval-tree'; const tree = new IntervalTree<string>(); const intervals: Array<[number, number]> = [[6,8],[1,4],[5,12],[1,1],[5,7]]; // Insert interval as a key and string "val0", "val1" etc. as a value intervals.forEach((iv, i) => { tree.insert(iv, "val"+i); }); // Get array of keys sorted in ascendant order const sorted_intervals = tree.keys; // expected array [[1,1],[1,4],[5,7],[5,12],[6,8]] // Search items which keys intersect with given interval, and return array of values const values_in_range = tree.search([2,3]); // expected array ['val1'] ``` ## Interval types The library supports multiple interval classes: - Interval (default export): 1D interval whose endpoints are comparable (number, bigint, string, Date). - Interval2D: a lexicographic 2D interval whose endpoints are points [x, y]. ```ts import IntervalTree, { Interval2D } from '@flatten-js/interval-tree'; // example of Interval2d tree which stores string values const tree = new IntervalTree<string>(); // 2D intervals (lexicographic ordering) const r1 = new Interval2D([0, 0], [10, 10]); const r2 = new Interval2D([5, 5], [15, 15]); tree.insert(r1, 'R1'); tree.insert(r2, 'R2'); ``` ### Constructor Create a new instance of the interval tree. In TypeScript, you may optionally specify the value type V for stronger type hints. ```ts // Without explicit value type (values default to unknown/any as inferred) const tree = new IntervalTree(); // With explicit value type V const treeStrings = new IntervalTree<string>(); const treeObjects = new IntervalTree<{ id: number; name: string }>(); ``` ### Insert(key[, value]) Insert a new item into the tree. Key is an interval object or a pair of numbers `[low, high]` (numeric pairs are normalized so that `low <= high`). If `value` is omitted, the key itself is stored as the value for convenience. If a node with an identical key already exists, the value is appended to that node’s bucket. Returns a reference to the inserted node. ```ts const node = tree.insert(key, value) ``` ### Exist(key[, value]) Returns `true` if the item exists in the tree. - Called as `exist(key)`, it checks whether a node with the given key exists (bucket may contain one or more values). - Called as `exist(key, value)`, it checks whether the specific value exists inside the bucket of that key (strict equality unless the value implements `equal_to`). ```ts const existsKey = tree.exist(key) const existsPair = tree.exist(key, value) ``` ### Remove(key[, value]) Removes entries from the tree. - Called as `remove(key)`, it removes the entire node for that key (i.e., the whole bucket). - Called as `remove(key, value)`, it removes a single matching value from the bucket; if the bucket becomes empty, the node is removed. Returns the removed node if something was deleted, or `undefined` if nothing was found. ```ts const removedNode = tree.remove(key) const removedPairNode = tree.remove(key, value) ``` ### Search(interval[, outputMapperFn]) Returns array of values which keys intersected with given interval. <br/> ```javascript const resp = tree.search(interval) ``` Optional *outputMapperFn(value, key)* enables to map search results into custom defined output. Example: ```javascript const composers = [ {name: "Ludwig van Beethoven", period: [1770, 1827]}, {name: "Johann Sebastian Bach", period: [1685, 1750]}, {name: "Wolfgang Amadeus Mozart", period: [1756, 1791]}, {name: "Johannes Brahms", period: [1833, 1897]}, {name: "Richard Wagner", period: [1813, 1883]}, {name: "Claude Debussy", period: [1862, 1918]}, {name: "Pyotr Ilyich Tchaikovsky", period: [1840, 1893]}, {name: "Frédéric Chopin", period: [1810, 1849]}, {name: "Joseph Haydn", period: [1732, 1809]}, {name: "Antonio Vivaldi", period: [1678, 1741]} ]; const tree = new IntervalTree(); for (const composer of composers) tree.insert(composer.period, composer.name); // Great composers who lived in 17th century const searchRes = tree.search( [1600,1700], (name, period) => {return `${name} (${period.low}-${period.high})`}); console.log(searchRes) // expected to be // [ 'Antonio Vivaldi (1678-1741)', 'Johann Sebastian Bach (1685-1750)' ] ``` ### Intersect_any(interval) Returns true if intersection found between given interval and any of intervals stored in the tree ```javascript const found = tree.intersect_any(interval) ``` ### Size Returns number of items stored in the tree (getter) ```javascript const size = tree.size ``` ### Keys Returns tree keys in ascendant order (getter) ```javascript const keys = tree.keys ``` ### Values Returns tree values in ascendant keys order (getter) ```javascript const values = tree.values ``` ### Items Returns items in ascendant keys order (getter) ```javascript const items = tree.items ``` ### ForEach(visitor) Enables to traverse the whole tree and perform operation for each item ```javascript tree.forEach( (key, value) => console.log(value) ) ``` ### Map(callback) Creates new tree with same keys using callback to transform (key,value) to a new value ```javascript let tree1 = tree.map((value, key) => (key.high-key.low)) ``` ### Clear() Clear tree ```javascript tree.clear() ``` ### Iterate([interval, outputMapperFn]) Returns an iterator (and iterable). <br/> Call `next` on the iterator to navigate to successor tree nodes and return the corresponding values. <br/> In the absence of a starting interval, the iterator will start with the lowest interval. <br/> ```javascript let iterator = tree.iterate(); let next = iterator.next().value; ``` Optional *outputMapperFn(value, key)* enables to map search results into custom defined output. <br/> Example: ```javascript let iterator = tree.iterate([5,5], (value, key) => key); let next_key = iterator.next().value; ``` Supports `for .. of` syntax. <br/> Example: ```javascript for (let key of tree.iterate([5,5], (value, key) => key)) { if (key[0] > 8) break; console.log(key); } ``` ## Documentation API reference is now generated with TypeDoc and published to GitHub Pages. - Live examples: https://alexbol99.github.io/flatten-interval-tree/examples.html - API docs: https://alexbol99.github.io/flatten-interval-tree/ Styling: The docs use TypeDoc’s default theme with a custom stylesheet (typedoc.theme.css) for improved typography, spacing, a branded accent color, and automatic dark mode. The top navigation also links to GitHub and the live examples page. To build docs locally: ```bash npm run docs ``` The output is written into the docs/ folder. ## Tests ```bash npm test ``` ## Contributors In lieu of a formal style guide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code. ## License MIT ## Support <a href="https://www.buymeacoffee.com/alexbol99" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="41" width="174"></a>