Skip to content

Commit

Permalink
Changes to use TernarySearchTree
Browse files Browse the repository at this point in the history
  • Loading branch information
Eric Amodio authored and eamodio committed Nov 12, 2020
1 parent 8a5605b commit 576d8af
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 52 deletions.
88 changes: 76 additions & 12 deletions src/vs/base/common/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,57 @@ export class StringIterator implements IKeyIterator<string> {
}
}

export class ConfigKeysIterator implements IKeyIterator<string> {

private _value!: string;
private _from!: number;
private _to!: number;

constructor(
private readonly _caseSensitive: boolean = true
) { }

reset(key: string): this {
this._value = key;
this._from = 0;
this._to = 0;
return this.next();
}

hasNext(): boolean {
return this._to < this._value.length;
}

next(): this {
// this._data = key.split(/[\\/]/).filter(s => !!s);
this._from = this._to;
let justSeps = true;
for (; this._to < this._value.length; this._to++) {
const ch = this._value.charCodeAt(this._to);
if (ch === CharCode.Period) {
if (justSeps) {
this._from++;
} else {
break;
}
} else {
justSeps = false;
}
}
return this;
}

cmp(a: string): number {
return this._caseSensitive
? compareSubstring(a, this._value, 0, a.length, this._from, this._to)
: compareSubstringIgnoreCase(a, this._value, 0, a.length, this._from, this._to);
}

value(): string {
return this._value.substring(this._from, this._to);
}
}

export class PathIterator implements IKeyIterator<string> {

private _value!: string;
Expand Down Expand Up @@ -238,6 +289,10 @@ export class TernarySearchTree<K, V> {
return new TernarySearchTree<string, E>(new StringIterator());
}

static forConfigKeys<E>(): TernarySearchTree<string, E> {
return new TernarySearchTree<string, E>(new ConfigKeysIterator());
}

private _iter: IKeyIterator<K>;
private _root: TernarySearchTreeNode<K, V> | undefined;

Expand Down Expand Up @@ -296,6 +351,10 @@ export class TernarySearchTree<K, V> {
}

get(key: K): V | undefined {
return this.getNode(key)?.value;
}

private getNode(key: K) {
const iter = this._iter.reset(key);
let node = this._root;
while (node) {
Expand All @@ -314,7 +373,11 @@ export class TernarySearchTree<K, V> {
break;
}
}
return node ? node.value : undefined;
return node;
}

has(key: K): boolean {
return !!this.getNode(key);
}

delete(key: K): void {
Expand Down Expand Up @@ -347,11 +410,18 @@ export class TernarySearchTree<K, V> {
stack.push([0, node]);
node = node.mid;
} else {
// remove element
node.value = undefined;
if (superStr) {
// remove children
node.left = undefined;
node.mid = undefined;
node.right = undefined;
} else {
// remove element
node.value = undefined;
}

// clean up empty nodes
while (stack.length > 0 && (node.isEmpty() || superStr)) {
while (stack.length > 0 && node.isEmpty()) {
let [dir, parent] = stack.pop()!;
switch (dir) {
case 1: parent.left = undefined; break;
Expand Down Expand Up @@ -389,7 +459,7 @@ export class TernarySearchTree<K, V> {
return node && node.value || candidate;
}

findSuperstr(key: K): Iterator<V> | undefined {
findSuperstr(key: K): IterableIterator<[K, V]> | undefined {
const iter = this._iter.reset(key);
let node = this._root;
while (node) {
Expand All @@ -409,7 +479,7 @@ export class TernarySearchTree<K, V> {
if (!node.mid) {
return undefined;
} else {
return this._values(node.mid);
return this._entries(node.mid);
}
}
}
Expand All @@ -426,12 +496,6 @@ export class TernarySearchTree<K, V> {
yield* this._entries(this._root);
}

private *_values(node: TernarySearchTreeNode<K, V>): IterableIterator<V> {
for (const [, value] of this._entries(node)) {
yield value;
}
}

private *_entries(node: TernarySearchTreeNode<K, V> | undefined): IterableIterator<[K, V]> {
if (node) {
// left
Expand Down
135 changes: 117 additions & 18 deletions src/vs/base/test/common/map.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { ResourceMap, TernarySearchTree, PathIterator, StringIterator, LinkedMap, Touch, LRUCache, UriIterator } from 'vs/base/common/map';
import { ResourceMap, TernarySearchTree, PathIterator, StringIterator, LinkedMap, Touch, LRUCache, UriIterator, ConfigKeysIterator } from 'vs/base/common/map';
import * as assert from 'assert';
import { URI } from 'vs/base/common/uri';
import { extUriIgnorePathCase } from 'vs/base/common/resources';
Expand Down Expand Up @@ -549,13 +549,15 @@ suite('Map', () => {
trie.set('foo', 1);
trie.set('foobar', 2);
trie.set('bar', 3);
trie.set('foobarbaz', 4);
trie.deleteSuperstr('foo');
assertTernarySearchTree(trie, ['bar', 3]);
assertTernarySearchTree(trie, ['foo', 1], ['bar', 3]);

trie = new TernarySearchTree<string, number>(new StringIterator());
trie.set('foo', 1);
trie.set('foobar', 2);
trie.set('bar', 3);
trie.set('foobarbaz', 4);
trie.deleteSuperstr('fo');
assertTernarySearchTree(trie, ['bar', 3]);

Expand Down Expand Up @@ -612,25 +614,25 @@ suite('Map', () => {
map.set('/user/foo/flip/flop', 3);
map.set('/usr/foo', 4);

let item: IteratorResult<number>;
let item: IteratorResult<[string, number]>;
let iter = map.findSuperstr('/user');

item = iter!.next();
assert.equal(item.value, 2);
assert.equal(item.value[1], 2);
assert.equal(item.done, false);
item = iter!.next();
assert.equal(item.value, 1);
assert.equal(item.value[1], 1);
assert.equal(item.done, false);
item = iter!.next();
assert.equal(item.value, 3);
assert.equal(item.value[1], 3);
assert.equal(item.done, false);
item = iter!.next();
assert.equal(item.value, undefined);
assert.equal(item.done, true);

iter = map.findSuperstr('/usr');
item = iter!.next();
assert.equal(item.value, 4);
assert.equal(item.value[1], 4);
assert.equal(item.done, false);

item = iter!.next();
Expand Down Expand Up @@ -675,7 +677,7 @@ suite('Map', () => {
map.set('/usr/foo', 4);
map.deleteSuperstr('/user/foo');
assertTernarySearchTree(map,
['/usr/foo', 4],
['/user/foo', 2], ['/usr/foo', 4],
);
});

Expand Down Expand Up @@ -725,33 +727,33 @@ suite('Map', () => {
assert.equal(map.get(URI.parse('foo://foo.bar/USER/foo/bar')), undefined);
});

test('TernarySearchTree (PathSegments) - superstr', function () {
test('TernarySearchTree (URI) - superstr', function () {

const map = new TernarySearchTree<URI, number>(new UriIterator(() => false));
map.set(URI.file('/user/foo/bar'), 1);
map.set(URI.file('/user/foo'), 2);
map.set(URI.file('/user/foo/flip/flop'), 3);
map.set(URI.file('/usr/foo'), 4);

let item: IteratorResult<number>;
let item: IteratorResult<[URI, number]>;
let iter = map.findSuperstr(URI.file('/user'))!;

item = iter.next();
assert.equal(item.value, 2);
assert.equal(item.value[1], 2);
assert.equal(item.done, false);
item = iter.next();
assert.equal(item.value, 1);
assert.equal(item.value[1], 1);
assert.equal(item.done, false);
item = iter.next();
assert.equal(item.value, 3);
assert.equal(item.value[1], 3);
assert.equal(item.done, false);
item = iter.next();
assert.equal(item.value, undefined);
assert.equal(item.done, true);

iter = map.findSuperstr(URI.file('/usr'))!;
item = iter.next();
assert.equal(item.value, 4);
assert.equal(item.value[1], 4);
assert.equal(item.done, false);

item = iter.next();
Expand All @@ -760,16 +762,16 @@ suite('Map', () => {

iter = map.findSuperstr(URI.file('/'))!;
item = iter.next();
assert.equal(item.value, 2);
assert.equal(item.value[1], 2);
assert.equal(item.done, false);
item = iter.next();
assert.equal(item.value, 1);
assert.equal(item.value[1], 1);
assert.equal(item.done, false);
item = iter.next();
assert.equal(item.value, 3);
assert.equal(item.value[1], 3);
assert.equal(item.done, false);
item = iter.next();
assert.equal(item.value, 4);
assert.equal(item.value[1], 4);
assert.equal(item.done, false);
item = iter.next();
assert.equal(item.value, undefined);
Expand All @@ -781,6 +783,103 @@ suite('Map', () => {
assert.equal(map.findSuperstr(URI.file('/userr')), undefined);
});

test('TernarySearchTree (ConfigKeySegments) - basics', function () {
let trie = new TernarySearchTree<string, number>(new ConfigKeysIterator());

trie.set('config.foo.bar', 1);
trie.set('config.foo', 2);
trie.set('config.foo.flip.flop', 3);

assert.equal(trie.get('config.foo.bar'), 1);
assert.equal(trie.get('config.foo'), 2);
assert.equal(trie.get('config.foo.flip.flop'), 3);

assert.equal(trie.findSubstr('config.bar'), undefined);
assert.equal(trie.findSubstr('config.foo'), 2);
assert.equal(trie.findSubstr('config.foo.ba'), 2);
assert.equal(trie.findSubstr('config.foo.far.boo'), 2);
assert.equal(trie.findSubstr('config.foo.bar'), 1);
assert.equal(trie.findSubstr('config.foo.bar.far.boo'), 1);
});

test('TernarySearchTree (ConfigKeySegments) - lookup', function () {

const map = new TernarySearchTree<string, number>(new ConfigKeysIterator());
map.set('config.foo.bar', 1);
map.set('config.foo', 2);
map.set('config.foo.flip.flop', 3);

assert.equal(map.get('foo'), undefined);
assert.equal(map.get('config'), undefined);
assert.equal(map.get('config.foo'), 2);
assert.equal(map.get('config.foo.bar'), 1);
assert.equal(map.get('config.foo.bar.boo'), undefined);
});

test('TernarySearchTree (ConfigKeySegments) - superstr', function () {

const map = new TernarySearchTree<string, number>(new ConfigKeysIterator());
map.set('config.foo.bar', 1);
map.set('config.foo', 2);
map.set('config.foo.flip.flop', 3);
map.set('boo', 4);

let item: IteratorResult<[string, number]>;
let iter = map.findSuperstr('config');

item = iter!.next();
assert.equal(item.value[1], 2);
assert.equal(item.done, false);
item = iter!.next();
assert.equal(item.value[1], 1);
assert.equal(item.done, false);
item = iter!.next();
assert.equal(item.value[1], 3);
assert.equal(item.done, false);
item = iter!.next();
assert.equal(item.value, undefined);
assert.equal(item.done, true);

assert.equal(map.findSuperstr('foo'), undefined);
assert.equal(map.findSuperstr('config.foo.no'), undefined);
assert.equal(map.findSuperstr('config.foop'), undefined);
});


test('TernarySearchTree (ConfigKeySegments) - delete_superstr', function () {

const map = new TernarySearchTree<string, number>(new ConfigKeysIterator());
map.set('config.foo.bar', 1);
map.set('config.foo', 2);
map.set('config.foo.flip.flop', 3);
map.set('boo', 4);

assertTernarySearchTree(map,
['config.foo.bar', 1],
['config.foo', 2],
['config.foo.flip.flop', 3],
['boo', 4],
);

// not a segment
map.deleteSuperstr('config.fo');
assertTernarySearchTree(map,
['config.foo.bar', 1],
['config.foo', 2],
['config.foo.flip.flop', 3],
['boo', 4],
);

// delete a segment
map.set('config.foo.bar', 1);
map.set('config.foo', 2);
map.set('config.foo.flip.flop', 3);
map.set('config.boo', 4);
map.deleteSuperstr('config.foo');
assertTernarySearchTree(map,
['config.foo', 2], ['boo', 4],
);
});

test('ResourceMap - basics', function () {
const map = new ResourceMap<any>();
Expand Down
Loading

0 comments on commit 576d8af

Please sign in to comment.