API Docs for:
Show:

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