src/vs/base/common/ternarySearchTree.ts
796 LOC · 774 covered · 22 uncovered · 263 ranges · 10093 concepts · 101 introducers · 5199 tests
File neighbourhood
The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.
Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file
In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.
Graph controls are ready.
Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.
/*---------------------------------------------------------------------------------------------
ternarySearchTree.ts ×60
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { shuffle } from './arrays.js';
import { assert } from './assert.js';
import { CharCode } from './charCode.js';
import { compare, compareIgnoreCase, compareSubstring, compareSubstringIgnoreCase } from './strings.js';
import { URI } from './uri.js';
export interface IKeyIterator<K> {
reset(key: K): this;
next(): this;
hasNext(): boolean;
cmp(a: string): number;
value(): string;
}
export class StringIterator implements IKeyIterator<string> {
private _value: string = '';
private _pos: number = 0;
reset(key: string): this {
this._pos = 0;
return this;
}
next(): this {
return this;
}
hasNext(): boolean {
}
cmp(a: string): number {
const thisCode = this._value.charCodeAt(this._pos);
return aCode - thisCode;
}
value(): string {
}
export class ConfigKeysIterator implements IKeyIterator<string> {
private _value!: string;
private _from!: number;
private _to!: number;
constructor(
) { }
reset(key: string): this {
this._from = 0;
this._to = 0;
return this.next();
}
hasNext(): boolean {
}
next(): this {
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 {
? compareSubstring(a, this._value, 0, a.length, this._from, this._to)
: compareSubstringIgnoreCase(a, this._value, 0, a.length, this._from, this._to);
value(): string {
}
export class PathIterator implements IKeyIterator<string> {
private _value!: string;
private _valueLen!: number;
private _from!: number;
private _to!: number;
constructor(
private readonly _caseSensitive: boolean = true
) { }
reset(key: string): this {
this._to = 0;
this._value = key;
this._valueLen = key.length;
for (let pos = key.length - 1; pos >= 0; pos--, this._valueLen--) {
const ch = this._value.charCodeAt(pos);
if (!(ch === CharCode.Slash || this._splitOnBackslash && ch === CharCode.Backslash)) {
break;
}
}
return this.next();
}
hasNext(): boolean {
}
next(): this {
this._from = this._to;
let justSeps = true;
for (; this._to < this._valueLen; this._to++) {
const ch = this._value.charCodeAt(this._to);
if (ch === CharCode.Slash || this._splitOnBackslash && ch === CharCode.Backslash) {
this._from++;
} else {
}
justSeps = false;
}
}
return this;
}
cmp(a: string): number {
: compareSubstringIgnoreCase(a, this._value, 0, a.length, this._from, this._to);
ternarySearchTree.ts ×1
value(): string {
}
const enum UriIteratorState {
Scheme = 1, Authority = 2, Path = 3, Query = 4, Fragment = 5
}
export class UriIterator implements IKeyIterator<URI> {
private _pathIterator!: PathIterator;
private _value!: URI;
private _states: UriIteratorState[] = [];
private _stateIdx: number = 0;
constructor(
private readonly _ignoreQueryAndFragment: (uri: URI) => boolean) { }
reset(key: URI): this {
this._states = [];
if (this._value.scheme) {
this._states.push(UriIteratorState.Scheme);
}
if (this._value.authority) {
}
this._pathIterator = new PathIterator(false, !this._ignorePathCasing(key));
this._pathIterator.reset(key.path);
if (this._pathIterator.value()) {
this._states.push(UriIteratorState.Path);
}
}
if (!this._ignoreQueryAndFragment(key)) {
}
this._states.push(UriIteratorState.Fragment);
}
return this;
}
next(): this {
if (this._states[this._stateIdx] === UriIteratorState.Path && this._pathIterator.hasNext()) {
ternarySearchTree.ts ×10
this._stateIdx += 1;
}
return this;
}
hasNext(): boolean {
return (this._states[this._stateIdx] === UriIteratorState.Path && this._pathIterator.hasNext())
ternarySearchTree.ts ×10
|| this._stateIdx < this._states.length - 1;
}
cmp(a: string): number {
return compareIgnoreCase(a, this._value.scheme);
} else if (this._states[this._stateIdx] === UriIteratorState.Authority) {
} else if (this._states[this._stateIdx] === UriIteratorState.Fragment) {
return compare(a, this._value.fragment);
}
throw new Error();
value(): string {
return this._value.scheme;
} else if (this._states[this._stateIdx] === UriIteratorState.Authority) {
return this._pathIterator.value();
} else if (this._states[this._stateIdx] === UriIteratorState.Query) {
} else if (this._states[this._stateIdx] === UriIteratorState.Fragment) {
return this._value.fragment;
}
throw new Error();
abstract class Undef {
static readonly Val: unique symbol = Symbol('undefined_placeholder');
static wrap<V>(value: V | undefined): V | typeof Undef.Val {
}
static unwrap<V>(value: V | typeof Undef.Val): V | undefined {
}
height: number = 1;
segment!: string;
value: V | typeof Undef.Val | undefined = undefined;
key: K | undefined = undefined;
left: TernarySearchTreeNode<K, V> | undefined = undefined;
mid: TernarySearchTreeNode<K, V> | undefined = undefined;
right: TernarySearchTreeNode<K, V> | undefined = undefined;
isEmpty(): boolean {
return !this.left && !this.mid && !this.right && this.value === undefined;
}
rotateLeft() {
this.right = tmp.left;
tmp.left = this;
this.updateHeight();
tmp.updateHeight();
return tmp;
}
rotateRight() {
this.left = tmp.right;
tmp.right = this;
this.updateHeight();
tmp.updateHeight();
return tmp;
}
updateHeight() {
}
balanceFactor() {
}
get heightLeft() {
}
get heightRight() {
}
const enum Dir {
Left = -1,
Mid = 0,
Right = 1
}
export class TernarySearchTree<K, V> {
static forUris<E>(ignorePathCasing: (key: URI) => boolean = () => false, ignoreQueryAndFragment: (key: URI) => boolean = () => false): TernarySearchTree<URI, E> {
return new TernarySearchTree<URI, E>(new UriIterator(ignorePathCasing, ignoreQueryAndFragment));
ternarySearchTree.ts ×1
}
static forPaths<E>(ignorePathCasing = false): TernarySearchTree<string, E> {
return new TernarySearchTree<string, E>(new PathIterator(undefined, !ignorePathCasing));
ternarySearchTree.ts ×1
}
static forStrings<E>(): TernarySearchTree<string, E> {
}
static forConfigKeys<E>(): TernarySearchTree<string, E> {
}
private _iter: IKeyIterator<K>;
private _root: TernarySearchTreeNode<K, V> | undefined;
constructor(segments: IKeyIterator<K>) {
}
clear(): void {
}
/**
* Fill the tree with the same value of the given keys
*/
fill(element: V, keys: readonly K[]): void;
/**
* Fill the tree with given [key,value]-tuples
*/
fill(values: readonly [K, V][]): void;
fill(values: readonly [K, V][] | V, keys?: readonly K[]): void {
shuffle(arr);
for (const k of arr) {
this.set(k, (<V>values));
}
shuffle(arr);
for (const entry of arr) {
this.set(entry[0], entry[1]);
}
}
set(key: K, element: V): V | undefined {
let node: TernarySearchTreeNode<K, V>;
if (!this._root) {
this._root = new TernarySearchTreeNode<K, V>();
this._root.segment = iter.value();
}
const stack: [Dir, TernarySearchTreeNode<K, V>][] = [];
// find insert_node
node = this._root;
while (true) {
const val = iter.cmp(node.segment);
if (val > 0) {
if (!node.left) {
node.left = new TernarySearchTreeNode<K, V>();
node.left.segment = iter.value();
}
stack.push([Dir.Left, node]);
node = node.left;
if (!node.right) {
node.right.segment = iter.value();
}
node = node.right;
iter.next();
if (!node.mid) {
node.mid = new TernarySearchTreeNode<K, V>();
node.mid.segment = iter.value();
}
stack.push([Dir.Mid, node]);
node = node.mid;
break;
}
}
// set value
const oldElement = Undef.unwrap(node.value);
node.value = Undef.wrap(element);
node.key = key;
// balance
for (let i = stack.length - 1; i >= 0; i--) {
node.updateHeight();
const bf = node.balanceFactor();
if (bf < -1 || bf > 1) {
const d1 = stack[i][0];
const d2 = stack[i + 1][0];
if (d1 === Dir.Right && d2 === Dir.Right) {
stack[i][1] = node.rotateLeft();
stack[i][1] = node.rotateRight();
node.right = stack[i + 1][1] = stack[i + 1][1].rotateRight();
stack[i][1] = node.rotateLeft();
node.left = stack[i + 1][1] = stack[i + 1][1].rotateLeft();
stack[i][1] = node.rotateRight();
} else {
throw new Error();
}
// patch path to parent
if (i > 0) {
case Dir.Left:
break;
break;
break;
}
return oldElement;
}
get(key: K): V | undefined {
}
private _getNode(key: K) {
let node = this._root;
while (node) {
if (val > 0) {
node = node.left;
node = node.right;
iter.next();
node = node.mid;
}
}
has(key: K): boolean {
return !(node?.value === undefined && node?.mid === undefined);
}
delete(key: K): void {
}
deleteSuperstr(key: K): void {
}
private _delete(key: K, superStr: boolean): void {
const stack: [Dir, TernarySearchTreeNode<K, V>][] = [];
let node = this._root;
// find node
while (node) {
const val = iter.cmp(node.segment);
if (val > 0) {
stack.push([Dir.Left, node]);
node = node.left;
stack.push([Dir.Right, node]);
node = node.right;
iter.next();
stack.push([Dir.Mid, node]);
node = node.mid;
break;
}
}
if (!node) {
return;
}
if (superStr) {
node.left = undefined;
node.mid = undefined;
node.right = undefined;
node.height = 1;
node.key = undefined;
node.value = undefined;
}
// BST node removal
if (!node.mid && !node.value) {
// replace deleted-node with the min-node of the right branch.
// If there is no true min-node leave things as they are
const stack2: typeof stack = [[Dir.Right, node]];
const min = this._min(node.right, stack2);
if (min.key) {
node.key = min.key;
node.value = min.value;
node.segment = min.segment;
// remove NODE (inorder successor can only have right child)
const newChild = min.right;
if (stack2.length > 1) {
switch (dir) {
case Dir.Left: parent.left = newChild; break;
case Dir.Mid: assert(false);
case Dir.Right: assert(false);
}
node.right = newChild;
}
// balance right branch and UPDATE parent pointer for stack
const newChild2 = this._balanceByStack(stack2)!;
if (stack.length > 0) {
switch (dir) {
case Dir.Left: parent.left = newChild2; break;
case Dir.Mid: parent.mid = newChild2; break;
case Dir.Right: parent.right = newChild2; break;
}
}
const newChild = node.left ?? node.right;
if (stack.length > 0) {
const [dir, parent] = stack[stack.length - 1];
switch (dir) {
case Dir.Left: parent.left = newChild; break;
case Dir.Mid: parent.mid = newChild; break;
case Dir.Right: parent.right = newChild; break;
}
} else {
}
// AVL balance
this._root = this._balanceByStack(stack) ?? this._root;
}
private _min(node: TernarySearchTreeNode<K, V>, stack: [Dir, TernarySearchTreeNode<K, V>][]): TernarySearchTreeNode<K, V> {
node = node.left;
}
}
private _balanceByStack(stack: [Dir, TernarySearchTreeNode<K, V>][]) {
for (let i = stack.length - 1; i >= 0; i--) {
const node = stack[i][1];
node.updateHeight();
const bf = node.balanceFactor();
if (bf > 1) {
if (node.right!.balanceFactor() >= 0) {
// right, right -> rotate left
stack[i][1] = node.rotateLeft();
} else {
node.right = node.right!.rotateRight();
stack[i][1] = node.rotateLeft();
}
if (node.left!.balanceFactor() <= 0) {
stack[i][1] = node.rotateRight();
node.left = node.left!.rotateLeft();
stack[i][1] = node.rotateRight();
}
// patch path to parent
if (i > 0) {
case Dir.Left:
break;
break;
stack[i - 1][1].mid = stack[i][1];
break;
}
return stack[0][1];
}
}
return undefined;
findSubstr(key: K): V | undefined {
let node = this._root;
let candidate: V | undefined = undefined;
while (node) {
if (val > 0) {
node = node.left;
node = node.right;
// mid
iter.next();
candidate = Undef.unwrap(node.value) || candidate;
node = node.mid;
} else {
}
}
findSuperstr(key: K): IterableIterator<[K, V]> | undefined {
}
private _findSuperstrOrElement(key: K, allowValue: true): IterableIterator<[K, V]> | V | undefined;
private _findSuperstrOrElement(key: K, allowValue: false): IterableIterator<[K, V]> | undefined;
private _findSuperstrOrElement(key: K, allowValue: boolean): IterableIterator<[K, V]> | V | undefined {
let node = this._root;
while (node) {
if (val > 0) {
node = node.left;
node = node.right;
iter.next();
node = node.mid;
if (!node.mid) {
if (allowValue) {
return Undef.unwrap(node.value);
} else {
return undefined;
}
return this._entries(node.mid);
}
}
}
hasElementOrSubtree(key: K): boolean {
return this._findSuperstrOrElement(key, true) !== undefined;
}
forEach(callback: (value: V, index: K) => unknown): void {
}
*[Symbol.iterator](): IterableIterator<[K, V]> {
}
private _entries(node: TernarySearchTreeNode<K, V> | undefined): IterableIterator<[K, V]> {
this._dfsEntries(node, result);
return result[Symbol.iterator]();
}
private _dfsEntries(node: TernarySearchTreeNode<K, V> | undefined, bucket: [K, V][]) {
if (!node) {
}
}
bucket.push([node.key!, Undef.unwrap(node.value)!]);
}
if (node.mid) {
}
}
// for debug/testing
_isBalanced(): boolean {
const nodeIsBalanced = (node: TernarySearchTreeNode<unknown, unknown> | undefined): boolean => {
ternarySearchTree.ts ×2
if (!node) {
return true;
}
const bf = node.balanceFactor();
if (bf < -1 || bf > 1) {
return false;
}
};
return nodeIsBalanced(this._root);
}