discourse/app/assets/javascripts/discourse-common/addon/lib/suffix-trie.js

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

88 lines
2.5 KiB
JavaScript
Raw Normal View History

class TrieNode {
constructor(name, parent) {
this.name = name;
this.parent = parent;
this.children = new Map();
this.leafIndex = null;
}
}
// Given a set of strings, this class can allow efficient lookups
// based on suffixes.
//
// By default, it will create one Trie node per character. If your data
// has known delimiters (e.g. / in file paths), you can pass a separator
// to the constructor for better performance.
//
// Matching results will be returned in insertion order
export default class SuffixTrie {
constructor(separator = "") {
this._trie = new TrieNode();
this.separator = separator;
this._nextIndex = 0;
}
add(value) {
const nodeNames = value.split(this.separator);
let currentNode = this._trie;
// Iterate over the nodes backwards. The last one should be
// at the root of the tree
for (let i = nodeNames.length - 1; i >= 0; i--) {
let newNode = currentNode.children.get(nodeNames[i]);
if (!newNode) {
newNode = new TrieNode(nodeNames[i], currentNode);
currentNode.children.set(nodeNames[i], newNode);
}
currentNode = newNode;
}
currentNode.leafIndex = this._nextIndex++;
}
withSuffix(suffix, resultCount = null) {
const nodeNames = suffix.split(this.separator);
// Traverse the tree to find the root node for this suffix
let node = this._trie;
for (let i = nodeNames.length - 1; i >= 0; i--) {
node = node.children.get(nodeNames[i]);
if (!node) {
return [];
}
}
// Find all the leaves which are descendents of that node
const leaves = [];
const descendentNodes = [node];
while (descendentNodes.length > 0) {
const thisDescendent = descendentNodes.pop();
if (thisDescendent.leafIndex !== null) {
leaves.push(thisDescendent);
}
descendentNodes.push(...thisDescendent.children.values());
}
// Sort them in-place according to insertion order
leaves.sort((a, b) => (a.leafIndex < b.leafIndex ? -1 : 1));
// If a subset of results have been requested, truncate
if (resultCount !== null) {
leaves.splice(resultCount);
}
// Calculate their full names, and return the joined string
return leaves.map((leafNode) => {
const parts = [leafNode.name];
let ancestorNode = leafNode;
while (typeof ancestorNode.parent?.name === "string") {
parts.push(ancestorNode.parent.name);
ancestorNode = ancestorNode.parent;
}
return parts.join(this.separator);
});
}
}