| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365 |
- "use strict"
- var bounds = require("binary-search-bounds")
- var NOT_FOUND = 0
- var SUCCESS = 1
- var EMPTY = 2
- module.exports = createWrapper
- function IntervalTreeNode(mid, left, right, leftPoints, rightPoints) {
- this.mid = mid
- this.left = left
- this.right = right
- this.leftPoints = leftPoints
- this.rightPoints = rightPoints
- this.count = (left ? left.count : 0) + (right ? right.count : 0) + leftPoints.length
- }
- var proto = IntervalTreeNode.prototype
- function copy(a, b) {
- a.mid = b.mid
- a.left = b.left
- a.right = b.right
- a.leftPoints = b.leftPoints
- a.rightPoints = b.rightPoints
- a.count = b.count
- }
- function rebuild(node, intervals) {
- var ntree = createIntervalTree(intervals)
- node.mid = ntree.mid
- node.left = ntree.left
- node.right = ntree.right
- node.leftPoints = ntree.leftPoints
- node.rightPoints = ntree.rightPoints
- node.count = ntree.count
- }
- function rebuildWithInterval(node, interval) {
- var intervals = node.intervals([])
- intervals.push(interval)
- rebuild(node, intervals)
- }
- function rebuildWithoutInterval(node, interval) {
- var intervals = node.intervals([])
- var idx = intervals.indexOf(interval)
- if(idx < 0) {
- return NOT_FOUND
- }
- intervals.splice(idx, 1)
- rebuild(node, intervals)
- return SUCCESS
- }
- proto.intervals = function(result) {
- result.push.apply(result, this.leftPoints)
- if(this.left) {
- this.left.intervals(result)
- }
- if(this.right) {
- this.right.intervals(result)
- }
- return result
- }
- proto.insert = function(interval) {
- var weight = this.count - this.leftPoints.length
- this.count += 1
- if(interval[1] < this.mid) {
- if(this.left) {
- if(4*(this.left.count+1) > 3*(weight+1)) {
- rebuildWithInterval(this, interval)
- } else {
- this.left.insert(interval)
- }
- } else {
- this.left = createIntervalTree([interval])
- }
- } else if(interval[0] > this.mid) {
- if(this.right) {
- if(4*(this.right.count+1) > 3*(weight+1)) {
- rebuildWithInterval(this, interval)
- } else {
- this.right.insert(interval)
- }
- } else {
- this.right = createIntervalTree([interval])
- }
- } else {
- var l = bounds.ge(this.leftPoints, interval, compareBegin)
- var r = bounds.ge(this.rightPoints, interval, compareEnd)
- this.leftPoints.splice(l, 0, interval)
- this.rightPoints.splice(r, 0, interval)
- }
- }
- proto.remove = function(interval) {
- var weight = this.count - this.leftPoints
- if(interval[1] < this.mid) {
- if(!this.left) {
- return NOT_FOUND
- }
- var rw = this.right ? this.right.count : 0
- if(4 * rw > 3 * (weight-1)) {
- return rebuildWithoutInterval(this, interval)
- }
- var r = this.left.remove(interval)
- if(r === EMPTY) {
- this.left = null
- this.count -= 1
- return SUCCESS
- } else if(r === SUCCESS) {
- this.count -= 1
- }
- return r
- } else if(interval[0] > this.mid) {
- if(!this.right) {
- return NOT_FOUND
- }
- var lw = this.left ? this.left.count : 0
- if(4 * lw > 3 * (weight-1)) {
- return rebuildWithoutInterval(this, interval)
- }
- var r = this.right.remove(interval)
- if(r === EMPTY) {
- this.right = null
- this.count -= 1
- return SUCCESS
- } else if(r === SUCCESS) {
- this.count -= 1
- }
- return r
- } else {
- if(this.count === 1) {
- if(this.leftPoints[0] === interval) {
- return EMPTY
- } else {
- return NOT_FOUND
- }
- }
- if(this.leftPoints.length === 1 && this.leftPoints[0] === interval) {
- if(this.left && this.right) {
- var p = this
- var n = this.left
- while(n.right) {
- p = n
- n = n.right
- }
- if(p === this) {
- n.right = this.right
- } else {
- var l = this.left
- var r = this.right
- p.count -= n.count
- p.right = n.left
- n.left = l
- n.right = r
- }
- copy(this, n)
- this.count = (this.left?this.left.count:0) + (this.right?this.right.count:0) + this.leftPoints.length
- } else if(this.left) {
- copy(this, this.left)
- } else {
- copy(this, this.right)
- }
- return SUCCESS
- }
- for(var l = bounds.ge(this.leftPoints, interval, compareBegin); l<this.leftPoints.length; ++l) {
- if(this.leftPoints[l][0] !== interval[0]) {
- break
- }
- if(this.leftPoints[l] === interval) {
- this.count -= 1
- this.leftPoints.splice(l, 1)
- for(var r = bounds.ge(this.rightPoints, interval, compareEnd); r<this.rightPoints.length; ++r) {
- if(this.rightPoints[r][1] !== interval[1]) {
- break
- } else if(this.rightPoints[r] === interval) {
- this.rightPoints.splice(r, 1)
- return SUCCESS
- }
- }
- }
- }
- return NOT_FOUND
- }
- }
- function reportLeftRange(arr, hi, cb) {
- for(var i=0; i<arr.length && arr[i][0] <= hi; ++i) {
- var r = cb(arr[i])
- if(r) { return r }
- }
- }
- function reportRightRange(arr, lo, cb) {
- for(var i=arr.length-1; i>=0 && arr[i][1] >= lo; --i) {
- var r = cb(arr[i])
- if(r) { return r }
- }
- }
- function reportRange(arr, cb) {
- for(var i=0; i<arr.length; ++i) {
- var r = cb(arr[i])
- if(r) { return r }
- }
- }
- proto.queryPoint = function(x, cb) {
- if(x < this.mid) {
- if(this.left) {
- var r = this.left.queryPoint(x, cb)
- if(r) { return r }
- }
- return reportLeftRange(this.leftPoints, x, cb)
- } else if(x > this.mid) {
- if(this.right) {
- var r = this.right.queryPoint(x, cb)
- if(r) { return r }
- }
- return reportRightRange(this.rightPoints, x, cb)
- } else {
- return reportRange(this.leftPoints, cb)
- }
- }
- proto.queryInterval = function(lo, hi, cb) {
- if(lo < this.mid && this.left) {
- var r = this.left.queryInterval(lo, hi, cb)
- if(r) { return r }
- }
- if(hi > this.mid && this.right) {
- var r = this.right.queryInterval(lo, hi, cb)
- if(r) { return r }
- }
- if(hi < this.mid) {
- return reportLeftRange(this.leftPoints, hi, cb)
- } else if(lo > this.mid) {
- return reportRightRange(this.rightPoints, lo, cb)
- } else {
- return reportRange(this.leftPoints, cb)
- }
- }
- function compareNumbers(a, b) {
- return a - b
- }
- function compareBegin(a, b) {
- var d = a[0] - b[0]
- if(d) { return d }
- return a[1] - b[1]
- }
- function compareEnd(a, b) {
- var d = a[1] - b[1]
- if(d) { return d }
- return a[0] - b[0]
- }
- function createIntervalTree(intervals) {
- if(intervals.length === 0) {
- return null
- }
- var pts = []
- for(var i=0; i<intervals.length; ++i) {
- pts.push(intervals[i][0], intervals[i][1])
- }
- pts.sort(compareNumbers)
- var mid = pts[pts.length>>1]
- var leftIntervals = []
- var rightIntervals = []
- var centerIntervals = []
- for(var i=0; i<intervals.length; ++i) {
- var s = intervals[i]
- if(s[1] < mid) {
- leftIntervals.push(s)
- } else if(mid < s[0]) {
- rightIntervals.push(s)
- } else {
- centerIntervals.push(s)
- }
- }
- //Split center intervals
- var leftPoints = centerIntervals
- var rightPoints = centerIntervals.slice()
- leftPoints.sort(compareBegin)
- rightPoints.sort(compareEnd)
- return new IntervalTreeNode(mid,
- createIntervalTree(leftIntervals),
- createIntervalTree(rightIntervals),
- leftPoints,
- rightPoints)
- }
- //User friendly wrapper that makes it possible to support empty trees
- function IntervalTree(root) {
- this.root = root
- }
- var tproto = IntervalTree.prototype
- tproto.insert = function(interval) {
- if(this.root) {
- this.root.insert(interval)
- } else {
- this.root = new IntervalTreeNode(interval[0], null, null, [interval], [interval])
- }
- }
- tproto.remove = function(interval) {
- if(this.root) {
- var r = this.root.remove(interval)
- if(r === EMPTY) {
- this.root = null
- }
- return r !== NOT_FOUND
- }
- return false
- }
- tproto.queryPoint = function(p, cb) {
- if(this.root) {
- return this.root.queryPoint(p, cb)
- }
- }
- tproto.queryInterval = function(lo, hi, cb) {
- if(lo <= hi && this.root) {
- return this.root.queryInterval(lo, hi, cb)
- }
- }
- Object.defineProperty(tproto, "count", {
- get: function() {
- if(this.root) {
- return this.root.count
- }
- return 0
- }
- })
- Object.defineProperty(tproto, "intervals", {
- get: function() {
- if(this.root) {
- return this.root.intervals([])
- }
- return []
- }
- })
- function createWrapper(intervals) {
- if(!intervals || intervals.length === 0) {
- return new IntervalTree(null)
- }
- return new IntervalTree(createIntervalTree(intervals))
- }
|