Skip to content

Commit

Permalink
Merge pull request #43 from amejiarosario/feature/trie
Browse files Browse the repository at this point in the history
feat(trie): implement trie data structure
  • Loading branch information
amejiarosario authored Mar 30, 2020
2 parents 567c110 + e31cc62 commit 965fbd1
Show file tree
Hide file tree
Showing 5 changed files with 579 additions and 0 deletions.
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

0 comments on commit 965fbd1

Please sign in to comment.