提交 bc76ef88 编写于 作者: A Alex Dima

Implement interval searching

上级 648e6f6a
......@@ -73,7 +73,7 @@ export class IntervalNode {
this.interval = interval;
this.maxEnd = this.interval.end;
this.resultInterval = null;
this.resultInterval = new Interval(0, 0);
}
public detach(): void {
......@@ -417,28 +417,51 @@ export class IntervalTree {
this.root = SENTINEL;
}
// public intervalSearch(interval: Interval): IntervalNode[] {
// let result: IntervalNode[] = [];
// if (this.root !== SENTINEL) {
// this._intervalSearch(this.root, 0, interval, result);
// }
// return result;
// }
public intervalSearch(interval: Interval): IntervalNode[] {
let result: IntervalNode[] = [];
if (this.root !== SENTINEL) {
this._intervalSearch(this.root, 0, interval.start, interval.end, result);
}
return result;
}
// private _intervalSearch(node: IntervalNode, delta: number, interval: Interval, result: IntervalNode[]): void {
// // https://en.wikipedia.org/wiki/Interval_tree#Augmented_tree
// // Now, it is known that two intervals A and B overlap only when both
// // A.low ≤ B.high and A.high ≥ B.low. When searching the trees for
// // nodes overlapping with a given interval, you can immediately skip:
// // - all nodes to the right of nodes whose low value is past the end of the given interval.
// // - all nodes that have their maximum 'high' value below the start of the given interval.
private _intervalSearch(node: IntervalNode, delta: number, intervalStart: number, intervalEnd: number, result: IntervalNode[]): void {
// https://en.wikipedia.org/wiki/Interval_tree#Augmented_tree
// Now, it is known that two intervals A and B overlap only when both
// A.low <= B.high and A.high >= B.low. When searching the trees for
// nodes overlapping with a given interval, you can immediately skip:
// a) all nodes to the right of nodes whose low value is past the end of the given interval.
// b) all nodes that have their maximum 'high' value below the start of the given interval.
// if (delta + node.maxEnd < interval.start) {
// return;
// }
const nodeMaxEnd = delta + node.maxEnd;
if (nodeMaxEnd < intervalStart) {
// Cover b) from above
return;
}
if (node.left !== SENTINEL) {
this._intervalSearch(node.left, delta, intervalStart, intervalEnd, result);
}
const nodeStart = delta + node.interval.start;
// }
if (nodeStart > intervalEnd) {
// Cover a) from above
return;
}
const nodeEnd = delta + node.interval.end;
if (nodeEnd >= intervalStart) {
// There is overlap
node.resultInterval.start = nodeStart;
node.resultInterval.end = nodeEnd;
result.push(node);
}
if (node.right !== SENTINEL) {
this._intervalSearch(node.right, delta + node.delta, intervalStart, intervalEnd, result);
}
}
public insert(interval: Interval): IntervalNode {
if (this.root === SENTINEL) {
......
......@@ -43,6 +43,17 @@ suite('IntervalTree', () => {
}
}
}
public search(interval: Interval): Interval[] {
let result: Interval[] = [];
for (let i = 0, len = this.intervals.length; i < len; i++) {
let int = this.intervals[i];
if (int.start <= interval.end && int.end >= interval.start) {
result.push(int);
}
}
return result;
}
}
class TestState {
......@@ -62,7 +73,7 @@ suite('IntervalTree', () => {
let nodeId = (++this._lastNodeId);
this._treeNodes[nodeId] = this._tree.insert(new Interval(op.begin, op.end));
this._oracleNodes[nodeId] = this._oracle.insert(new Interval(op.begin, op.end));
} else {
} else if (op.type === 'delete') {
if (PRINT_TREE) {
console.log(`delete: {${JSON.stringify(this._oracleNodes[op.id])}}`);
}
......@@ -71,6 +82,12 @@ suite('IntervalTree', () => {
this._treeNodes[op.id] = null;
this._oracleNodes[op.id] = null;
} else {
let actualNodes = this._tree.intervalSearch(new Interval(op.begin, op.end));
let actual = actualNodes.map(n => n.resultInterval);
let expected = this._oracle.search(new Interval(op.begin, op.end));
assert.deepEqual(actual, expected);
return;
}
if (PRINT_TREE) {
......@@ -110,7 +127,13 @@ suite('IntervalTree', () => {
id: number;
}
type IOperation = IInsertOperation | IDeleteOperation;
interface ISearchOperation {
type: 'search';
begin: number;
end: number;
}
type IOperation = IInsertOperation | IDeleteOperation | ISearchOperation;
function testIntervalTree(ops: IOperation[]): void {
let state = new TestState();
......@@ -123,6 +146,19 @@ suite('IntervalTree', () => {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function getRandomRange(min: number, max: number): [number, number] {
let begin = getRandomInt(min, max);
let length: number;
if (getRandomInt(1, 10) <= 2) {
// large range
length = getRandomInt(0, max - begin);
} else {
// small range
length = getRandomInt(0, Math.min(max - begin, 10));
}
return [begin, begin + length];
}
class AutoTest {
private _ops: IOperation[] = [];
private _state: TestState = new TestState();
......@@ -143,19 +179,11 @@ suite('IntervalTree', () => {
type = 'delete';
}
if (type === 'insert') {
let begin = getRandomInt(MIN_INTERVAL_START, MAX_INTERVAL_END);
let length: number;
if (getRandomInt(1, 10) <= 2) {
// large range
length = getRandomInt(0, MAX_INTERVAL_END - begin);
} else {
// small range
length = getRandomInt(0, Math.min(MAX_INTERVAL_END - begin, 10));
}
let range = getRandomRange(MIN_INTERVAL_START, MAX_INTERVAL_END);
this._run({
type: 'insert',
begin: begin,
end: begin + length
begin: range[0],
end: range[1]
});
this._insertCnt--;
this._deleteCnt++;
......@@ -166,8 +194,15 @@ suite('IntervalTree', () => {
id: this._state.getExistingNodeId(idx)
});
this._deleteCnt--;
// this._deleteCnt = 0;
}
// Let's also search for something...
let searchRange = getRandomRange(MIN_INTERVAL_START, MAX_INTERVAL_END);
this._run({
type: 'search',
begin: searchRange[0],
end: searchRange[1]
});
}
}
......@@ -182,167 +217,169 @@ suite('IntervalTree', () => {
}
test('gen01', () => {
testIntervalTree([
{ type: 'insert', begin: 28, end: 35 },
{ type: 'insert', begin: 52, end: 54 },
{ type: 'insert', begin: 63, end: 69 }
]);
});
test('gen02', () => {
testIntervalTree([
{ type: 'insert', begin: 80, end: 89 },
{ type: 'insert', begin: 92, end: 100 },
{ type: 'insert', begin: 99, end: 99 }
]);
});
test('gen03', () => {
testIntervalTree([
{ type: 'insert', begin: 89, end: 96 },
{ type: 'insert', begin: 71, end: 74 },
{ type: 'delete', id: 1 }
]);
});
test('gen04', () => {
testIntervalTree([
{ type: 'insert', begin: 44, end: 46 },
{ type: 'insert', begin: 85, end: 88 },
{ type: 'delete', id: 0 }
]);
});
test('gen05', () => {
testIntervalTree([
{ type: 'insert', begin: 82, end: 90 },
{ type: 'insert', begin: 69, end: 73 },
{ type: 'delete', id: 0 },
{ type: 'delete', id: 1 }
]);
});
test('gen06', () => {
testIntervalTree([
{ type: 'insert', begin: 41, end: 63 },
{ type: 'insert', begin: 98, end: 98 },
{ type: 'insert', begin: 47, end: 51 },
{ type: 'delete', id: 2 }
]);
});
test('gen07', () => {
testIntervalTree([
{ type: 'insert', begin: 24, end: 26 },
{ type: 'insert', begin: 11, end: 28 },
{ type: 'insert', begin: 27, end: 30 },
{ type: 'insert', begin: 80, end: 85 },
{ type: 'delete', id: 1 }
]);
});
test('gen08', () => {
testIntervalTree([
{ type: 'insert', begin: 100, end: 100 },
{ type: 'insert', begin: 100, end: 100 }
]);
});
test('gen09', () => {
testIntervalTree([
{ type: 'insert', begin: 58, end: 65 },
{ type: 'insert', begin: 82, end: 96 },
{ type: 'insert', begin: 58, end: 65 }
]);
});
test('gen10', () => {
testIntervalTree([
{ type: 'insert', begin: 32, end: 40 },
{ type: 'insert', begin: 25, end: 29 },
{ type: 'insert', begin: 24, end: 32 }
]);
});
test('gen11', () => {
testIntervalTree([
{ type: 'insert', begin: 25, end: 70 },
{ type: 'insert', begin: 99, end: 100 },
{ type: 'insert', begin: 46, end: 51 },
{ type: 'insert', begin: 57, end: 57 },
{ type: 'delete', id: 2 }
]);
});
test('gen12', () => {
testIntervalTree([
{ type: 'insert', begin: 20, end: 26 },
{ type: 'insert', begin: 10, end: 18 },
{ type: 'insert', begin: 99, end: 99 },
{ type: 'insert', begin: 37, end: 59 },
{ type: 'delete', id: 2 }
]);
});
test('gen13', () => {
testIntervalTree([
{ type: 'insert', begin: 3, end: 91 },
{ type: 'insert', begin: 57, end: 57 },
{ type: 'insert', begin: 35, end: 44 },
{ type: 'insert', begin: 72, end: 81 },
{ type: 'delete', id: 2 }
]);
});
test('gen14', () => {
testIntervalTree([
{ type: 'insert', begin: 58, end: 61 },
{ type: 'insert', begin: 34, end: 35 },
{ type: 'insert', begin: 56, end: 62 },
{ type: 'insert', begin: 69, end: 78 },
{ type: 'delete', id: 0 }
]);
});
test('gen15', () => {
testIntervalTree([
{ type: 'insert', begin: 63, end: 69 },
{ type: 'insert', begin: 17, end: 24 },
{ type: 'insert', begin: 3, end: 13 },
{ type: 'insert', begin: 84, end: 94 },
{ type: 'insert', begin: 18, end: 23 },
{ type: 'insert', begin: 96, end: 98 },
{ type: 'delete', id: 1 }
]);
});
test('gen16', () => {
testIntervalTree([
{ type: 'insert', begin: 27, end: 27 },
{ type: 'insert', begin: 42, end: 87 },
{ type: 'insert', begin: 42, end: 49 },
{ type: 'insert', begin: 69, end: 71 },
{ type: 'insert', begin: 20, end: 27 },
{ type: 'insert', begin: 8, end: 9 },
{ type: 'insert', begin: 42, end: 49 },
{ type: 'delete', id: 1 }
]);
});
test('gen17', () => {
testIntervalTree([
{ type: 'insert', begin: 21, end: 23 },
{ type: 'insert', begin: 83, end: 87 },
{ type: 'insert', begin: 56, end: 58 },
{ type: 'insert', begin: 1, end: 55 },
{ type: 'insert', begin: 56, end: 59 },
{ type: 'insert', begin: 58, end: 60 },
{ type: 'insert', begin: 56, end: 65 },
{ type: 'delete', id: 1 },
{ type: 'delete', id: 0 },
{ type: 'delete', id: 6 }
]);
suite('generated', () => {
test('gen01', () => {
testIntervalTree([
{ type: 'insert', begin: 28, end: 35 },
{ type: 'insert', begin: 52, end: 54 },
{ type: 'insert', begin: 63, end: 69 }
]);
});
test('gen02', () => {
testIntervalTree([
{ type: 'insert', begin: 80, end: 89 },
{ type: 'insert', begin: 92, end: 100 },
{ type: 'insert', begin: 99, end: 99 }
]);
});
test('gen03', () => {
testIntervalTree([
{ type: 'insert', begin: 89, end: 96 },
{ type: 'insert', begin: 71, end: 74 },
{ type: 'delete', id: 1 }
]);
});
test('gen04', () => {
testIntervalTree([
{ type: 'insert', begin: 44, end: 46 },
{ type: 'insert', begin: 85, end: 88 },
{ type: 'delete', id: 0 }
]);
});
test('gen05', () => {
testIntervalTree([
{ type: 'insert', begin: 82, end: 90 },
{ type: 'insert', begin: 69, end: 73 },
{ type: 'delete', id: 0 },
{ type: 'delete', id: 1 }
]);
});
test('gen06', () => {
testIntervalTree([
{ type: 'insert', begin: 41, end: 63 },
{ type: 'insert', begin: 98, end: 98 },
{ type: 'insert', begin: 47, end: 51 },
{ type: 'delete', id: 2 }
]);
});
test('gen07', () => {
testIntervalTree([
{ type: 'insert', begin: 24, end: 26 },
{ type: 'insert', begin: 11, end: 28 },
{ type: 'insert', begin: 27, end: 30 },
{ type: 'insert', begin: 80, end: 85 },
{ type: 'delete', id: 1 }
]);
});
test('gen08', () => {
testIntervalTree([
{ type: 'insert', begin: 100, end: 100 },
{ type: 'insert', begin: 100, end: 100 }
]);
});
test('gen09', () => {
testIntervalTree([
{ type: 'insert', begin: 58, end: 65 },
{ type: 'insert', begin: 82, end: 96 },
{ type: 'insert', begin: 58, end: 65 }
]);
});
test('gen10', () => {
testIntervalTree([
{ type: 'insert', begin: 32, end: 40 },
{ type: 'insert', begin: 25, end: 29 },
{ type: 'insert', begin: 24, end: 32 }
]);
});
test('gen11', () => {
testIntervalTree([
{ type: 'insert', begin: 25, end: 70 },
{ type: 'insert', begin: 99, end: 100 },
{ type: 'insert', begin: 46, end: 51 },
{ type: 'insert', begin: 57, end: 57 },
{ type: 'delete', id: 2 }
]);
});
test('gen12', () => {
testIntervalTree([
{ type: 'insert', begin: 20, end: 26 },
{ type: 'insert', begin: 10, end: 18 },
{ type: 'insert', begin: 99, end: 99 },
{ type: 'insert', begin: 37, end: 59 },
{ type: 'delete', id: 2 }
]);
});
test('gen13', () => {
testIntervalTree([
{ type: 'insert', begin: 3, end: 91 },
{ type: 'insert', begin: 57, end: 57 },
{ type: 'insert', begin: 35, end: 44 },
{ type: 'insert', begin: 72, end: 81 },
{ type: 'delete', id: 2 }
]);
});
test('gen14', () => {
testIntervalTree([
{ type: 'insert', begin: 58, end: 61 },
{ type: 'insert', begin: 34, end: 35 },
{ type: 'insert', begin: 56, end: 62 },
{ type: 'insert', begin: 69, end: 78 },
{ type: 'delete', id: 0 }
]);
});
test('gen15', () => {
testIntervalTree([
{ type: 'insert', begin: 63, end: 69 },
{ type: 'insert', begin: 17, end: 24 },
{ type: 'insert', begin: 3, end: 13 },
{ type: 'insert', begin: 84, end: 94 },
{ type: 'insert', begin: 18, end: 23 },
{ type: 'insert', begin: 96, end: 98 },
{ type: 'delete', id: 1 }
]);
});
test('gen16', () => {
testIntervalTree([
{ type: 'insert', begin: 27, end: 27 },
{ type: 'insert', begin: 42, end: 87 },
{ type: 'insert', begin: 42, end: 49 },
{ type: 'insert', begin: 69, end: 71 },
{ type: 'insert', begin: 20, end: 27 },
{ type: 'insert', begin: 8, end: 9 },
{ type: 'insert', begin: 42, end: 49 },
{ type: 'delete', id: 1 }
]);
});
test('gen17', () => {
testIntervalTree([
{ type: 'insert', begin: 21, end: 23 },
{ type: 'insert', begin: 83, end: 87 },
{ type: 'insert', begin: 56, end: 58 },
{ type: 'insert', begin: 1, end: 55 },
{ type: 'insert', begin: 56, end: 59 },
{ type: 'insert', begin: 58, end: 60 },
{ type: 'insert', begin: 56, end: 65 },
{ type: 'delete', id: 1 },
{ type: 'delete', id: 0 },
{ type: 'delete', id: 6 }
]);
});
});
// TEST_COUNT = 0;
......@@ -362,4 +399,84 @@ suite('IntervalTree', () => {
return;
}
}
suite('searching', () => {
function createCormenTree(): IntervalTree {
let r = new IntervalTree();
let data: [number, number][] = [
[16, 21],
[8, 9],
[25, 30],
[5, 8],
[15, 23],
[17, 19],
[26, 26],
[0, 3],
[6, 10],
[19, 20]
];
data.forEach((int) => {
r.insert(new Interval(int[0], int[1]));
});
return r;
}
const T = createCormenTree();
function assertIntervalSearch(start: number, end: number, expected: [number, number][]): void {
let actualNodes = T.intervalSearch(new Interval(start, end));
let actual = actualNodes.map((n) => <[number, number]>[n.resultInterval.start, n.resultInterval.end]);
assert.deepEqual(actual, expected);
}
test('cormen 1->2', () => {
assertIntervalSearch(
1, 2,
[
[0, 3],
]
);
});
test('cormen 4->8', () => {
assertIntervalSearch(
4, 8,
[
[5, 8],
[6, 10],
[8, 9],
]
);
});
test('cormen 10->15', () => {
assertIntervalSearch(
10, 15,
[
[6, 10],
[15, 23],
]
);
});
test('cormen 21->25', () => {
assertIntervalSearch(
21, 25,
[
[15, 23],
[16, 21],
[25, 30],
]
);
});
test('cormen 24->24', () => {
assertIntervalSearch(
24, 24,
[
]
);
});
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册