From bc76ef88b9c09d6757e95a11e1060dd0227074e0 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 12 Oct 2017 16:29:09 +0200 Subject: [PATCH] Implement interval searching --- src/vs/editor/common/model/intervalTree.ts | 61 ++- .../test/common/model/intervalTree.test.ts | 467 +++++++++++------- 2 files changed, 334 insertions(+), 194 deletions(-) diff --git a/src/vs/editor/common/model/intervalTree.ts b/src/vs/editor/common/model/intervalTree.ts index fb9b9294352..60d9f8ca73d 100644 --- a/src/vs/editor/common/model/intervalTree.ts +++ b/src/vs/editor/common/model/intervalTree.ts @@ -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) { diff --git a/src/vs/editor/test/common/model/intervalTree.test.ts b/src/vs/editor/test/common/model/intervalTree.test.ts index 3c5caac63fc..ec6d1dcfe1a 100644 --- a/src/vs/editor/test/common/model/intervalTree.test.ts +++ b/src/vs/editor/test/common/model/intervalTree.test.ts @@ -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, + [ + ] + ); + }); + }); }); -- GitLab