blob: 98291c5042fb62ab1139c8cf51f497e4277abb5b [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @unrestricted
*/
Common.Trie = class {
constructor() {
this.clear();
}
/**
* @param {string} word
*/
add(word) {
let node = this._root;
++this._wordsInSubtree[this._root];
for (let i = 0; i < word.length; ++i) {
const edge = word[i];
let next = this._edges[node][edge];
if (!next) {
if (this._freeNodes.length) {
// No need to reset any fields since they were properly cleaned up in remove().
next = this._freeNodes.pop();
} else {
next = this._size++;
this._isWord.push(false);
this._wordsInSubtree.push(0);
this._edges.push({__proto__: null});
}
this._edges[node][edge] = next;
}
++this._wordsInSubtree[next];
node = next;
}
this._isWord[node] = true;
}
/**
* @param {string} word
* @return {boolean}
*/
remove(word) {
if (!this.has(word))
return false;
let node = this._root;
--this._wordsInSubtree[this._root];
for (let i = 0; i < word.length; ++i) {
const edge = word[i];
const next = this._edges[node][edge];
if (!--this._wordsInSubtree[next]) {
delete this._edges[node][edge];
this._freeNodes.push(next);
}
node = next;
}
this._isWord[node] = false;
return true;
}
/**
* @param {string} word
* @return {boolean}
*/
has(word) {
let node = this._root;
for (let i = 0; i < word.length; ++i) {
node = this._edges[node][word[i]];
if (!node)
return false;
}
return this._isWord[node];
}
/**
* @param {string=} prefix
* @return {!Array<string>}
*/
words(prefix) {
prefix = prefix || '';
let node = this._root;
for (let i = 0; i < prefix.length; ++i) {
node = this._edges[node][prefix[i]];
if (!node)
return [];
}
const results = [];
this._dfs(node, prefix, results);
return results;
}
/**
* @param {number} node
* @param {string} prefix
* @param {!Array<string>} results
*/
_dfs(node, prefix, results) {
if (this._isWord[node])
results.push(prefix);
const edges = this._edges[node];
for (const edge in edges)
this._dfs(edges[edge], prefix + edge, results);
}
/**
* @param {string} word
* @param {boolean} fullWordOnly
* @return {string}
*/
longestPrefix(word, fullWordOnly) {
let node = this._root;
let wordIndex = 0;
for (let i = 0; i < word.length; ++i) {
node = this._edges[node][word[i]];
if (!node)
break;
if (!fullWordOnly || this._isWord[node])
wordIndex = i + 1;
}
return word.substring(0, wordIndex);
}
clear() {
this._size = 1;
this._root = 0;
/** @type {!Array<!Object<string, number>>} */
this._edges = [{__proto__: null}];
/** @type {!Array<boolean>} */
this._isWord = [false];
/** @type {!Array<number>} */
this._wordsInSubtree = [0];
/** @type {!Array<number>} */
this._freeNodes = [];
}
};