interval-tree.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. "use strict"
  2. var bounds = require("binary-search-bounds")
  3. var NOT_FOUND = 0
  4. var SUCCESS = 1
  5. var EMPTY = 2
  6. module.exports = createWrapper
  7. function IntervalTreeNode(mid, left, right, leftPoints, rightPoints) {
  8. this.mid = mid
  9. this.left = left
  10. this.right = right
  11. this.leftPoints = leftPoints
  12. this.rightPoints = rightPoints
  13. this.count = (left ? left.count : 0) + (right ? right.count : 0) + leftPoints.length
  14. }
  15. var proto = IntervalTreeNode.prototype
  16. function copy(a, b) {
  17. a.mid = b.mid
  18. a.left = b.left
  19. a.right = b.right
  20. a.leftPoints = b.leftPoints
  21. a.rightPoints = b.rightPoints
  22. a.count = b.count
  23. }
  24. function rebuild(node, intervals) {
  25. var ntree = createIntervalTree(intervals)
  26. node.mid = ntree.mid
  27. node.left = ntree.left
  28. node.right = ntree.right
  29. node.leftPoints = ntree.leftPoints
  30. node.rightPoints = ntree.rightPoints
  31. node.count = ntree.count
  32. }
  33. function rebuildWithInterval(node, interval) {
  34. var intervals = node.intervals([])
  35. intervals.push(interval)
  36. rebuild(node, intervals)
  37. }
  38. function rebuildWithoutInterval(node, interval) {
  39. var intervals = node.intervals([])
  40. var idx = intervals.indexOf(interval)
  41. if(idx < 0) {
  42. return NOT_FOUND
  43. }
  44. intervals.splice(idx, 1)
  45. rebuild(node, intervals)
  46. return SUCCESS
  47. }
  48. proto.intervals = function(result) {
  49. result.push.apply(result, this.leftPoints)
  50. if(this.left) {
  51. this.left.intervals(result)
  52. }
  53. if(this.right) {
  54. this.right.intervals(result)
  55. }
  56. return result
  57. }
  58. proto.insert = function(interval) {
  59. var weight = this.count - this.leftPoints.length
  60. this.count += 1
  61. if(interval[1] < this.mid) {
  62. if(this.left) {
  63. if(4*(this.left.count+1) > 3*(weight+1)) {
  64. rebuildWithInterval(this, interval)
  65. } else {
  66. this.left.insert(interval)
  67. }
  68. } else {
  69. this.left = createIntervalTree([interval])
  70. }
  71. } else if(interval[0] > this.mid) {
  72. if(this.right) {
  73. if(4*(this.right.count+1) > 3*(weight+1)) {
  74. rebuildWithInterval(this, interval)
  75. } else {
  76. this.right.insert(interval)
  77. }
  78. } else {
  79. this.right = createIntervalTree([interval])
  80. }
  81. } else {
  82. var l = bounds.ge(this.leftPoints, interval, compareBegin)
  83. var r = bounds.ge(this.rightPoints, interval, compareEnd)
  84. this.leftPoints.splice(l, 0, interval)
  85. this.rightPoints.splice(r, 0, interval)
  86. }
  87. }
  88. proto.remove = function(interval) {
  89. var weight = this.count - this.leftPoints
  90. if(interval[1] < this.mid) {
  91. if(!this.left) {
  92. return NOT_FOUND
  93. }
  94. var rw = this.right ? this.right.count : 0
  95. if(4 * rw > 3 * (weight-1)) {
  96. return rebuildWithoutInterval(this, interval)
  97. }
  98. var r = this.left.remove(interval)
  99. if(r === EMPTY) {
  100. this.left = null
  101. this.count -= 1
  102. return SUCCESS
  103. } else if(r === SUCCESS) {
  104. this.count -= 1
  105. }
  106. return r
  107. } else if(interval[0] > this.mid) {
  108. if(!this.right) {
  109. return NOT_FOUND
  110. }
  111. var lw = this.left ? this.left.count : 0
  112. if(4 * lw > 3 * (weight-1)) {
  113. return rebuildWithoutInterval(this, interval)
  114. }
  115. var r = this.right.remove(interval)
  116. if(r === EMPTY) {
  117. this.right = null
  118. this.count -= 1
  119. return SUCCESS
  120. } else if(r === SUCCESS) {
  121. this.count -= 1
  122. }
  123. return r
  124. } else {
  125. if(this.count === 1) {
  126. if(this.leftPoints[0] === interval) {
  127. return EMPTY
  128. } else {
  129. return NOT_FOUND
  130. }
  131. }
  132. if(this.leftPoints.length === 1 && this.leftPoints[0] === interval) {
  133. if(this.left && this.right) {
  134. var p = this
  135. var n = this.left
  136. while(n.right) {
  137. p = n
  138. n = n.right
  139. }
  140. if(p === this) {
  141. n.right = this.right
  142. } else {
  143. var l = this.left
  144. var r = this.right
  145. p.count -= n.count
  146. p.right = n.left
  147. n.left = l
  148. n.right = r
  149. }
  150. copy(this, n)
  151. this.count = (this.left?this.left.count:0) + (this.right?this.right.count:0) + this.leftPoints.length
  152. } else if(this.left) {
  153. copy(this, this.left)
  154. } else {
  155. copy(this, this.right)
  156. }
  157. return SUCCESS
  158. }
  159. for(var l = bounds.ge(this.leftPoints, interval, compareBegin); l<this.leftPoints.length; ++l) {
  160. if(this.leftPoints[l][0] !== interval[0]) {
  161. break
  162. }
  163. if(this.leftPoints[l] === interval) {
  164. this.count -= 1
  165. this.leftPoints.splice(l, 1)
  166. for(var r = bounds.ge(this.rightPoints, interval, compareEnd); r<this.rightPoints.length; ++r) {
  167. if(this.rightPoints[r][1] !== interval[1]) {
  168. break
  169. } else if(this.rightPoints[r] === interval) {
  170. this.rightPoints.splice(r, 1)
  171. return SUCCESS
  172. }
  173. }
  174. }
  175. }
  176. return NOT_FOUND
  177. }
  178. }
  179. function reportLeftRange(arr, hi, cb) {
  180. for(var i=0; i<arr.length && arr[i][0] <= hi; ++i) {
  181. var r = cb(arr[i])
  182. if(r) { return r }
  183. }
  184. }
  185. function reportRightRange(arr, lo, cb) {
  186. for(var i=arr.length-1; i>=0 && arr[i][1] >= lo; --i) {
  187. var r = cb(arr[i])
  188. if(r) { return r }
  189. }
  190. }
  191. function reportRange(arr, cb) {
  192. for(var i=0; i<arr.length; ++i) {
  193. var r = cb(arr[i])
  194. if(r) { return r }
  195. }
  196. }
  197. proto.queryPoint = function(x, cb) {
  198. if(x < this.mid) {
  199. if(this.left) {
  200. var r = this.left.queryPoint(x, cb)
  201. if(r) { return r }
  202. }
  203. return reportLeftRange(this.leftPoints, x, cb)
  204. } else if(x > this.mid) {
  205. if(this.right) {
  206. var r = this.right.queryPoint(x, cb)
  207. if(r) { return r }
  208. }
  209. return reportRightRange(this.rightPoints, x, cb)
  210. } else {
  211. return reportRange(this.leftPoints, cb)
  212. }
  213. }
  214. proto.queryInterval = function(lo, hi, cb) {
  215. if(lo < this.mid && this.left) {
  216. var r = this.left.queryInterval(lo, hi, cb)
  217. if(r) { return r }
  218. }
  219. if(hi > this.mid && this.right) {
  220. var r = this.right.queryInterval(lo, hi, cb)
  221. if(r) { return r }
  222. }
  223. if(hi < this.mid) {
  224. return reportLeftRange(this.leftPoints, hi, cb)
  225. } else if(lo > this.mid) {
  226. return reportRightRange(this.rightPoints, lo, cb)
  227. } else {
  228. return reportRange(this.leftPoints, cb)
  229. }
  230. }
  231. function compareNumbers(a, b) {
  232. return a - b
  233. }
  234. function compareBegin(a, b) {
  235. var d = a[0] - b[0]
  236. if(d) { return d }
  237. return a[1] - b[1]
  238. }
  239. function compareEnd(a, b) {
  240. var d = a[1] - b[1]
  241. if(d) { return d }
  242. return a[0] - b[0]
  243. }
  244. function createIntervalTree(intervals) {
  245. if(intervals.length === 0) {
  246. return null
  247. }
  248. var pts = []
  249. for(var i=0; i<intervals.length; ++i) {
  250. pts.push(intervals[i][0], intervals[i][1])
  251. }
  252. pts.sort(compareNumbers)
  253. var mid = pts[pts.length>>1]
  254. var leftIntervals = []
  255. var rightIntervals = []
  256. var centerIntervals = []
  257. for(var i=0; i<intervals.length; ++i) {
  258. var s = intervals[i]
  259. if(s[1] < mid) {
  260. leftIntervals.push(s)
  261. } else if(mid < s[0]) {
  262. rightIntervals.push(s)
  263. } else {
  264. centerIntervals.push(s)
  265. }
  266. }
  267. //Split center intervals
  268. var leftPoints = centerIntervals
  269. var rightPoints = centerIntervals.slice()
  270. leftPoints.sort(compareBegin)
  271. rightPoints.sort(compareEnd)
  272. return new IntervalTreeNode(mid,
  273. createIntervalTree(leftIntervals),
  274. createIntervalTree(rightIntervals),
  275. leftPoints,
  276. rightPoints)
  277. }
  278. //User friendly wrapper that makes it possible to support empty trees
  279. function IntervalTree(root) {
  280. this.root = root
  281. }
  282. var tproto = IntervalTree.prototype
  283. tproto.insert = function(interval) {
  284. if(this.root) {
  285. this.root.insert(interval)
  286. } else {
  287. this.root = new IntervalTreeNode(interval[0], null, null, [interval], [interval])
  288. }
  289. }
  290. tproto.remove = function(interval) {
  291. if(this.root) {
  292. var r = this.root.remove(interval)
  293. if(r === EMPTY) {
  294. this.root = null
  295. }
  296. return r !== NOT_FOUND
  297. }
  298. return false
  299. }
  300. tproto.queryPoint = function(p, cb) {
  301. if(this.root) {
  302. return this.root.queryPoint(p, cb)
  303. }
  304. }
  305. tproto.queryInterval = function(lo, hi, cb) {
  306. if(lo <= hi && this.root) {
  307. return this.root.queryInterval(lo, hi, cb)
  308. }
  309. }
  310. Object.defineProperty(tproto, "count", {
  311. get: function() {
  312. if(this.root) {
  313. return this.root.count
  314. }
  315. return 0
  316. }
  317. })
  318. Object.defineProperty(tproto, "intervals", {
  319. get: function() {
  320. if(this.root) {
  321. return this.root.intervals([])
  322. }
  323. return []
  324. }
  325. })
  326. function createWrapper(intervals) {
  327. if(!intervals || intervals.length === 0) {
  328. return new IntervalTree(null)
  329. }
  330. return new IntervalTree(createIntervalTree(intervals))
  331. }