You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

156 lines
4.2 KiB

import { RangeCov } from "./types";
export class RangeTree {
start: number;
end: number;
delta: number;
children: RangeTree[];
constructor(
start: number,
end: number,
delta: number,
children: RangeTree[],
) {
this.start = start;
this.end = end;
this.delta = delta;
this.children = children;
}
/**
* @precodition `ranges` are well-formed and pre-order sorted
*/
static fromSortedRanges(ranges: ReadonlyArray<RangeCov>): RangeTree | undefined {
let root: RangeTree | undefined;
// Stack of parent trees and parent counts.
const stack: [RangeTree, number][] = [];
for (const range of ranges) {
const node: RangeTree = new RangeTree(range.startOffset, range.endOffset, range.count, []);
if (root === undefined) {
root = node;
stack.push([node, range.count]);
continue;
}
let parent: RangeTree;
let parentCount: number;
while (true) {
[parent, parentCount] = stack[stack.length - 1];
// assert: `top !== undefined` (the ranges are sorted)
if (range.startOffset < parent.end) {
break;
} else {
stack.pop();
}
}
node.delta -= parentCount;
parent.children.push(node);
stack.push([node, range.count]);
}
return root;
}
normalize(): void {
const children: RangeTree[] = [];
let curEnd: number;
let head: RangeTree | undefined;
const tail: RangeTree[] = [];
for (const child of this.children) {
if (head === undefined) {
head = child;
} else if (child.delta === head.delta && child.start === curEnd!) {
tail.push(child);
} else {
endChain();
head = child;
}
curEnd = child.end;
}
if (head !== undefined) {
endChain();
}
if (children.length === 1) {
const child: RangeTree = children[0];
if (child.start === this.start && child.end === this.end) {
this.delta += child.delta;
this.children = child.children;
// `.lazyCount` is zero for both (both are after normalization)
return;
}
}
this.children = children;
function endChain(): void {
if (tail.length !== 0) {
head!.end = tail[tail.length - 1].end;
for (const tailTree of tail) {
for (const subChild of tailTree.children) {
subChild.delta += tailTree.delta - head!.delta;
head!.children.push(subChild);
}
}
tail.length = 0;
}
head!.normalize();
children.push(head!);
}
}
/**
* @precondition `tree.start < value && value < tree.end`
* @return RangeTree Right part
*/
split(value: number): RangeTree {
let leftChildLen: number = this.children.length;
let mid: RangeTree | undefined;
// TODO(perf): Binary search (check overhead)
for (let i: number = 0; i < this.children.length; i++) {
const child: RangeTree = this.children[i];
if (child.start < value && value < child.end) {
mid = child.split(value);
leftChildLen = i + 1;
break;
} else if (child.start >= value) {
leftChildLen = i;
break;
}
}
const rightLen: number = this.children.length - leftChildLen;
const rightChildren: RangeTree[] = this.children.splice(leftChildLen, rightLen);
if (mid !== undefined) {
rightChildren.unshift(mid);
}
const result: RangeTree = new RangeTree(
value,
this.end,
this.delta,
rightChildren,
);
this.end = value;
return result;
}
/**
* Get the range coverages corresponding to the tree.
*
* The ranges are pre-order sorted.
*/
toRanges(): RangeCov[] {
const ranges: RangeCov[] = [];
// Stack of parent trees and counts.
const stack: [RangeTree, number][] = [[this, 0]];
while (stack.length > 0) {
const [cur, parentCount]: [RangeTree, number] = stack.pop()!;
const count: number = parentCount + cur.delta;
ranges.push({startOffset: cur.start, endOffset: cur.end, count});
for (let i: number = cur.children.length - 1; i >= 0; i--) {
stack.push([cur.children[i], count]);
}
}
return ranges;
}
}