range-tree.ts
  1  import { RangeCov } from "./types";
  2  
  3  export class RangeTree {
  4    start: number;
  5    end: number;
  6    delta: number;
  7    children: RangeTree[];
  8  
  9    constructor(
 10      start: number,
 11      end: number,
 12      delta: number,
 13      children: RangeTree[],
 14    ) {
 15      this.start = start;
 16      this.end = end;
 17      this.delta = delta;
 18      this.children = children;
 19    }
 20  
 21    /**
 22     * @precodition `ranges` are well-formed and pre-order sorted
 23     */
 24    static fromSortedRanges(ranges: ReadonlyArray<RangeCov>): RangeTree | undefined {
 25      let root: RangeTree | undefined;
 26      // Stack of parent trees and parent counts.
 27      const stack: [RangeTree, number][] = [];
 28      for (const range of ranges) {
 29        const node: RangeTree = new RangeTree(range.startOffset, range.endOffset, range.count, []);
 30        if (root === undefined) {
 31          root = node;
 32          stack.push([node, range.count]);
 33          continue;
 34        }
 35        let parent: RangeTree;
 36        let parentCount: number;
 37        while (true) {
 38          [parent, parentCount] = stack[stack.length - 1];
 39          // assert: `top !== undefined` (the ranges are sorted)
 40          if (range.startOffset < parent.end) {
 41            break;
 42          } else {
 43            stack.pop();
 44          }
 45        }
 46        node.delta -= parentCount;
 47        parent.children.push(node);
 48        stack.push([node, range.count]);
 49      }
 50      return root;
 51    }
 52  
 53    normalize(): void {
 54      const children: RangeTree[] = [];
 55      let curEnd: number;
 56      let head: RangeTree | undefined;
 57      const tail: RangeTree[] = [];
 58      for (const child of this.children) {
 59        if (head === undefined) {
 60          head = child;
 61        } else if (child.delta === head.delta && child.start === curEnd!) {
 62          tail.push(child);
 63        } else {
 64          endChain();
 65          head = child;
 66        }
 67        curEnd = child.end;
 68      }
 69      if (head !== undefined) {
 70        endChain();
 71      }
 72  
 73      if (children.length === 1) {
 74        const child: RangeTree = children[0];
 75        if (child.start === this.start && child.end === this.end) {
 76          this.delta += child.delta;
 77          this.children = child.children;
 78          // `.lazyCount` is zero for both (both are after normalization)
 79          return;
 80        }
 81      }
 82  
 83      this.children = children;
 84  
 85      function endChain(): void {
 86        if (tail.length !== 0) {
 87          head!.end = tail[tail.length - 1].end;
 88          for (const tailTree of tail) {
 89            for (const subChild of tailTree.children) {
 90              subChild.delta += tailTree.delta - head!.delta;
 91              head!.children.push(subChild);
 92            }
 93          }
 94          tail.length = 0;
 95        }
 96        head!.normalize();
 97        children.push(head!);
 98      }
 99    }
100  
101    /**
102     * @precondition `tree.start < value && value < tree.end`
103     * @return RangeTree Right part
104     */
105    split(value: number): RangeTree {
106      let leftChildLen: number = this.children.length;
107      let mid: RangeTree | undefined;
108  
109      // TODO(perf): Binary search (check overhead)
110      for (let i: number = 0; i < this.children.length; i++) {
111        const child: RangeTree = this.children[i];
112        if (child.start < value && value < child.end) {
113          mid = child.split(value);
114          leftChildLen = i + 1;
115          break;
116        } else if (child.start >= value) {
117          leftChildLen = i;
118          break;
119        }
120      }
121  
122      const rightLen: number = this.children.length - leftChildLen;
123      const rightChildren: RangeTree[] = this.children.splice(leftChildLen, rightLen);
124      if (mid !== undefined) {
125        rightChildren.unshift(mid);
126      }
127      const result: RangeTree = new RangeTree(
128        value,
129        this.end,
130        this.delta,
131        rightChildren,
132      );
133      this.end = value;
134      return result;
135    }
136  
137    /**
138     * Get the range coverages corresponding to the tree.
139     *
140     * The ranges are pre-order sorted.
141     */
142    toRanges(): RangeCov[] {
143      const ranges: RangeCov[] = [];
144      // Stack of parent trees and counts.
145      const stack: [RangeTree, number][] = [[this, 0]];
146      while (stack.length > 0) {
147        const [cur, parentCount]: [RangeTree, number] = stack.pop()!;
148        const count: number = parentCount + cur.delta;
149        ranges.push({startOffset: cur.start, endOffset: cur.end, count});
150        for (let i: number = cur.children.length - 1; i >= 0; i--) {
151          stack.push([cur.children[i], count]);
152        }
153      }
154      return ranges;
155    }
156  }