Skip to content

feat(trie): implement trie data structure #43

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions src/data-structures/trees/trie-1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
class Trie {
constructor(val) {
this.val = val;
this.children = {};
this.isWord = false;
}

/**
* Insert word into trie and mark last element as such.
* @param {string} word
* @return {undefined}
*/
insert(word) {
let curr = this;

for (const char of word) {
curr.children[char] = curr.children[char] || new Trie(char);
curr = curr.children[char];
}

curr.isWord = true;
}

/**
* Search for complete word (by default) or partial if flag is set.
* @param {string} word - Word to search.
* @param {boolean} options.partial - Whether or not match partial matches.
* @return {boolean}
*/
search(word, { partial } = {}) {
let curr = this;

for (const char of word) {
if (!curr.children[char]) { return false; }
curr = curr.children[char];
}

return partial ? true : curr.isWord;
}

/**
* Return true if any word on the trie starts with the given prefix
* @param {string} prefix - Partial word to search.
* @return {boolean}
*/
startsWith(prefix) {
return this.search(prefix, { partial: true });
}

/**
* Returns all the words from the current `node`.
* Uses backtracking.
*
* @param {string} prefix - The prefix to append to each word.
* @param {string} node - Current node to start backtracking.
* @param {string[]} words - Accumulated words.
* @param {string} string - Current string.
*/
getAllWords(prefix = '', node = this, words = [], string = '') {
if (node.isWord) {
words.push(`${prefix}${string}`);
}

for (const char of Object.keys(node.children)) {
this.getAllWords(prefix, node.children[char], words, `${string}${char}`);
}

return words;
}

/**
* Return true if found the word to be removed, otherwise false.
* Iterative approach
* @param {string} word - The word to remove
* @returns {boolean}
*/
remove(word) {
const stack = [];
let curr = this;

for (const char of word) {
if (!curr.children[char]) { return false; }
stack.push(curr);
curr = curr.children[char];
}

if (!curr.isWord) { return false; }
let node = stack.pop();

do {
node.children = {};
node = stack.pop();
} while (node && !node.isWord);

return true;
}

/**
* Return true if found the word to be removed, otherwise false.
* recursive approach
* @param {string} word - The word to remove
* @returns {boolean}
*/
remove2(word, i = 0, parent = this) {
if (i === word.length - 1) {
return true;
}
const child = parent.children[word.charAt(i)];
if (!child) return false;

const found = this.remove(word, i + 1, child);

if (found) {
delete parent.children[word.charAt(i)];
}
return true;
}
}

// Aliases
Trie.prototype.add = Trie.prototype.insert;

module.exports = Trie;
133 changes: 133 additions & 0 deletions src/data-structures/trees/trie-2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
class Trie {
constructor(val) {
this.val = val;
this.children = {};
this.isWord = false;
}

/**
* Insert word into trie and mark last element as such.
* @param {string} word
* @return {undefined}
*/
insert(word) {
let curr = this;

for (const char of word) {
curr.children[char] = curr.children[char] || new Trie(char);
curr = curr.children[char];
}

curr.isWord = true;
}

/**
* Return true if found the word to be removed, otherwise false.
* @param {string} word - The word to remove
* @returns {boolean}
*/
remove(word) {
return this.removeHelper(word);
}

/**
* Remove word from trie, return true if found, otherwise false.
* @param {string} word - The word to remove.
* @param {Trie} parent - The parent node.
* @param {number} index - The index.
* @param {number} meta.stop - Keeps track of the last letter that won't be removed.
* @returns {boolean}
*/
removeHelper(word, parent = this, index = 0, meta = { stop: 0 }) {
if (index === word.length) {
parent.isWord = false;
if (Object.keys(parent.children)) { meta.stop = index; }
return true;
}
const child = parent.children[word.charAt(index)];
if (!child) { return false; }
if (parent.isWord) { meta.stop = index; }
const found = this.removeHelper(word, child, index + 1, meta);
// deletes all the nodes beyond `meta.stop`.
if (found && index >= meta.stop) {
delete parent.children[word.charAt(index)];
}
return found;
}

/**
* Retun last node that matches word or prefix or false if not found.
* @param {string} word - Word to search.
* @param {boolean} options.partial - Whether or not match partial matches.
* @return {Trie|false}
*/
searchNode(word) {
let curr = this;

for (const char of word) {
if (!curr.children[char]) { return false; }
curr = curr.children[char];
}

return curr;
}

/**
* Search for complete word (by default) or partial if flag is set.
* @param {string} word - Word to search.
* @param {boolean} options.partial - Whether or not match partial matches.
* @return {boolean}
*/
search(word, { partial } = {}) {
const curr = this.searchNode(word);
if (!curr) { return false; }
return partial ? true : curr.isWord;
}

/**
* Return true if any word on the trie starts with the given prefix
* @param {string} prefix - Partial word to search.
* @return {boolean}
*/
startsWith(prefix) {
return this.search(prefix, { partial: true });
}

/**
* Returns all the words from the current `node`.
* Uses backtracking.
*
* @param {string} prefix - The prefix to append to each word.
* @param {string} node - Current node to start backtracking.
*/
getAllWords(prefix = '', node = this) {
let words = [];

if (!node) { return words; }
if (node.isWord) {
words.push(prefix);
}

for (const char of Object.keys(node.children)) {
const newWords = this.getAllWords(`${prefix}${char}`, node.children[char]);
words = words.concat(newWords);
}

return words;
}

/**
* Return a list of words matching the prefix
* @param {*} prefix - The prefix to match.
* @returns {string[]}
*/
autocomplete(prefix = '') {
const curr = this.searchNode(prefix);
return this.getAllWords(prefix, curr);
}
}

// Aliases
Trie.prototype.add = Trie.prototype.insert;

module.exports = Trie;
132 changes: 132 additions & 0 deletions src/data-structures/trees/trie.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
class Trie {
constructor(val) {
this.val = val;
this.children = {};
this.isWord = false;
}

/**
* Insert word into trie and mark last element as such.
* @param {string} word
* @return {undefined}
*/
insert(word) {
let curr = this;

for (const char of word) {
curr.children[char] = curr.children[char] || new Trie(char);
curr = curr.children[char];
}

curr.isWord = true;
}

/**
* Return true if found the word to be removed, otherwise false.
* @param {string} word - The word to remove
* @returns {boolean}
*/
remove(word) {
let curr = this;
// let lastWordToKeep = 0;
const stack = [curr];

// find word and stack path
for (const char of word) {
if (!curr.children[char]) { return false; }
// lastWordToKeep += 1;
curr = curr.children[char];
stack.push(curr);
}

let child = stack.pop();
child.isWord = false;

// remove non words without children
while (stack.length) {
const parent = stack.pop();
if (!child.isWord && !Object.keys(child.children).length) {
delete parent.children[child.val];
}
child = parent;
}

return true;
}

/**
* Retun last node that matches word or prefix or false if not found.
* @param {string} word - Word to search.
* @param {boolean} options.partial - Whether or not match partial matches.
* @return {Trie|false}
*/
searchNode(word) {
let curr = this;

for (const char of word) {
if (!curr.children[char]) { return false; }
curr = curr.children[char];
}

return curr;
}

/**
* Search for complete word (by default) or partial if flag is set.
* @param {string} word - Word to search.
* @param {boolean} options.partial - Whether or not match partial matches.
* @return {boolean}
*/
search(word, { partial } = {}) {
const curr = this.searchNode(word);
if (!curr) { return false; }
return partial ? true : curr.isWord;
}

/**
* Return true if any word on the trie starts with the given prefix
* @param {string} prefix - Partial word to search.
* @return {boolean}
*/
startsWith(prefix) {
return this.search(prefix, { partial: true });
}

/**
* Returns all the words from the current `node`.
* Uses backtracking.
*
* @param {string} prefix - The prefix to append to each word.
* @param {string} node - Current node to start backtracking.
*/
getAllWords(prefix = '', node = this) {
let words = [];

if (!node) { return words; }
if (node.isWord) {
words.push(prefix);
}

for (const char of Object.keys(node.children)) {
const newWords = this.getAllWords(`${prefix}${char}`, node.children[char]);
words = words.concat(newWords);
}

return words;
}

/**
* Return a list of words matching the prefix
* @param {*} prefix - The prefix to match.
* @returns {string[]}
*/
autocomplete(prefix = '') {
const curr = this.searchNode(prefix);
return this.getAllWords(prefix, curr);
}
}

// Aliases
Trie.prototype.add = Trie.prototype.insert;

module.exports = Trie;
Loading