@dcoffey/espells
Version:
Pure JS/TS spellchecker, using Hunspell dictionaries. Based on Spylls.
103 lines • 3.59 kB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
const END_WORD_SYMBOL = Symbol("$");
/** Classic trie data structure. */
export class Trie {
constructor() {
/** Entrypoint for the trie. */
Object.defineProperty(this, "head", {
enumerable: true,
configurable: true,
writable: true,
value: new Map()
});
}
/**
* Adds a word to the trie, which will point to a value.
*
* @param word - The word to add.
* @param addValue - A callback function for how to add the word to the
* trie. It is provided the current value of the word added, if any.
* This allows for merging overlapping values.
*/
add(word, addValue) {
const current = this.traverse(word, true);
current.set(END_WORD_SYMBOL, !addValue ? word : addValue(current.get(END_WORD_SYMBOL)));
}
/** Determines if a word is in the trie. */
has(word) {
const ret = this.traverse(word);
return Boolean(ret.value) && !ret.isSubstring;
}
/** Returns the segments found when traversing the trie for the given word. */
segments(word) {
const ret = this.traverse(word);
return !ret.segments.length ? null : ret.segments;
}
/** Returns the longest/last segment for a word. */
lastSegment(word) {
const segments = this.segments(word);
return !segments ? null : segments[segments.length - 1];
}
/** Removes a word from the trie. */
remove(word) {
this.tryDelete(word, -1, this.head);
}
traverse(word, add = false) {
let current = this.head;
const segments = [];
for (let i = 0; i < word.length; i++) {
if (current.has(END_WORD_SYMBOL)) {
segments.push(current.get(END_WORD_SYMBOL));
}
if (!current.get(word[i])) {
if (add) {
current.set(word[i], new Map());
}
else {
return {
value: current.get(END_WORD_SYMBOL),
word: word.substring(0, i),
segments,
isSubstring: true
};
}
}
// keep traversing
current = current.get(word[i]);
}
// fully stripped word
if (current.has(END_WORD_SYMBOL)) {
segments.push(current.get(END_WORD_SYMBOL));
}
if (add) {
return current;
}
// found the word
return {
value: current.get(END_WORD_SYMBOL),
word: word,
segments,
isSubstring: false
};
}
tryDelete(word = "", index = 0, node = null) {
if (index >= word.length) {
throw new Error("Bad index to check for deletion.");
}
if (node === null) {
throw new Error(`Bad Node at ${index} for ${word}`);
}
const currentNode = node;
if (index === word.length - 1) {
return currentNode.delete(END_WORD_SYMBOL) && node.size === 0;
}
const newIndex = word[index + 1];
if (this.tryDelete(word, index + 1, node.get(newIndex))) {
return currentNode.delete(END_WORD_SYMBOL) && node.size === 0;
}
return false;
}
}
//# sourceMappingURL=trie.js.map