File: src/lib/interval-tree.coffee
SortedList = require './sorted-list'
Node = require './node'
Point = require './point'
Interval = require './interval'
Util = require './util'
###*
interval tree
@class IntervalTree
@module interval-tree2
###
class IntervalTree
###*
@constructor
@param {Number} center center of the root node
###
constructor: (center) ->
Util.assertNumber center, 'IntervalTree: center'
###*
center => node
@property {Object(Node)} nodesByCenter
###
@nodesByCenter = {}
###*
root node
@property {Node} root
###
@root = @createNode(center)
###*
interval id => interval
@property {Object(Interval)} intervalsById
###
@intervalsById = {}
###*
interval id => node
@property {Object(Node)} nodesById
###
@nodesById = {}
###*
sorted list of whole point
@property {SortedList(Point)} pointTree
###
@pointTree = new SortedList('val')
###*
unique id candidate of interval without id to be added next time
@property {Number} idCandidate
###
@idCandidate = 0
###*
add one interval
@method add
@public
@param {Number} start start of the interval to create
@param {Number} end end of the interval to create
@param {String|Number} [id] identifier to distinguish intervals. Automatically defiend when not set.
@return {Interval}
###
add: (start, end, id) ->
if @intervalsById[id]?
throw new Error('id ' + id + ' is already registered.')
if not id?
while @intervalsById[@idCandidate]?
@idCandidate++
id = @idCandidate
Util.assertNumber(start, '1st argument of IntervalTree#add()')
Util.assertNumber(end, '2nd argument of IntervalTree#add()')
if start >= end
Util.assertOrder(start, end, 'start', 'end')
interval = new Interval(start, end, id)
@pointTree.insert new Point(interval.start, id)
@pointTree.insert new Point(interval.end, id)
@intervalsById[id] = interval
return @insert interval, @root
###*
search intervals
when only one argument is given, return intervals which contains the value
when two arguments are given, ...
@method search
@public
@param {Number} val1
@param {Number} val2
@return {Array(Interval)} intervals
###
search: (val1, val2) ->
Util.assertNumber val1, '1st argument at IntervalTree#search()'
if not val2?
return @pointSearch val1
else
Util.assertNumber val2, '2nd argument at IntervalTree#search()'
Util.assertOrder val1, val2, '1st argument', '2nd argument', 'IntervalTree#search()'
return @rangeSearch val1, val2
###*
removes an interval of the given id
@method remove
@public
@param {Number|String} id id of the interval to remove
###
remove: (id) ->
interval = @intervalsById[id]
return if not interval?
node = @nodesById[id]
node.remove(interval)
delete @nodesById[id]
delete @intervalsById[id]
###*
search intervals at the given node
@method pointSearch
@public
@param {Number} val
@param {Node} [node] current node to search. default is this.root
@return {Array(Interval)}
###
pointSearch: (val, node = @root, results = []) ->
Util.assertNumber(val, '1st argument of IntervalTree#pointSearch()')
if val < node.center
results = results.concat node.startPointSearch(val)
if node.left?
return @pointSearch(val, node.left, results)
else
return results
if val > node.center
results = results.concat node.endPointSearch(val)
if node.right?
return @pointSearch(val, node.right, results)
else
return results
# if val is node.center
return results.concat node.getAllIntervals()
###*
returns intervals which covers the given start-end interval
@method rangeSearch
@public
@param {Number} start start of the interval
@param {Number} end end of the interval
@return {Array(Interval)}
###
rangeSearch: (start, end) ->
Util.assertNumber start, '1st argument at IntervalTree#rangeSearch()'
Util.assertNumber end, '2nd argument at IntervalTree#rangeSearch()'
Util.assertOrder start, end, '1st argument', '2nd argument', 'IntervalTree#rangeSearch()'
resultsById = {}
# 1. pointSearch to arbitrary point between start and end (here: point = start)
for interval in @pointSearch(start)
resultsById[interval.id] = interval
# 2. add intervals whose either point is included in the given range
firstPos = @pointTree.firstPositionOf new Point(start)
lastPos = @pointTree.lastPositionOf new Point(end)
for point in @pointTree.slice(firstPos, lastPos + 1)
resultsById[point.id] = @intervalsById[point.id]
return (interval for id, interval of resultsById)
###*
insert interval to the given node
@method insert
@private
@param {Interval} interval
@param {Node} node node to insert the interval
@return {Interval} inserted interval
###
insert: (interval, node) ->
if interval.end < node.center
node.left ?= @createNode(interval.end)
return @insert(interval, node.left)
if node.center < interval.start
node.right ?= @createNode(interval.start)
return @insert(interval, node.right)
node.insert interval
@nodesById[interval.id] = node
return interval
###*
create node by center
@method createNode
@private
@param {Number} center
@return {Node} node
###
createNode: (center) ->
node = new Node(center)
@nodesByCenter[center] = node
return node
module.exports = IntervalTree