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 1 commit
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
Prev Previous commit
Next Next commit
feat(trie): remove method
  • Loading branch information
amejiarosario committed Mar 27, 2020
commit a81f6e1d57ee07d8ee1dc6b5abb2f6486cb15505
48 changes: 48 additions & 0 deletions src/data-structures/trees/trie-1.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,54 @@ class Trie {

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
Expand Down
34 changes: 34 additions & 0 deletions src/data-structures/trees/trie.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,40 @@ class Trie {
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.
Expand Down
55 changes: 55 additions & 0 deletions src/data-structures/trees/trie.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ describe('Trie', () => {
expect(trie.startsWith('do')).toEqual(true);
});

it('should match full words if partial is set', () => {
expect(trie.search('dogs', {
partial: true,
})).toEqual(true);
expect(trie.startsWith('dogs')).toEqual(true);
});

it('should not match non existing words', () => {
expect(trie.search('doors')).toEqual(false);
});
Expand Down Expand Up @@ -129,5 +136,53 @@ describe('Trie', () => {
expect(words).toEqual([]);
});
});

describe('remove', () => {
it('should remove a word', () => {
trie = new Trie();
trie.insert('a');
expect(trie.remove('a')).toEqual(true);
expect(trie.getAllWords()).toEqual([]);
});

it('should remove word and keep other words', () => {
trie = new Trie();
trie.insert('a');
trie.insert('ab');
expect(trie.remove('a')).toEqual(true);
expect(trie.getAllWords()).toEqual(['ab']);
});

it('should remove surrounding word', () => {
trie = new Trie();
trie.insert('a');
trie.insert('ab');
expect(trie.remove('ab')).toEqual(true);
expect(trie.getAllWords()).toEqual(['a']);
});

it('should return false when word is not found', () => {
expect(trie.remove('not there')).toBe(false);
});

it('should remove words in between and still match', () => {
expect(trie.remove('dog')).toBe(true);
expect(trie.search('dogs')).toBe(true);
expect(trie.startsWith('dog')).toBe(true);
expect(trie.getAllWords()).toEqual([
'dogs', 'door', 'day', 'cat',
]);
});

it('should remove word and no longer match partials', () => {
expect(trie.remove('dogs')).toBe(true);
expect(trie.search('dogs')).toBe(false);
expect(trie.search('dog')).toBe(true);
expect(trie.startsWith('dog')).toBe(true);
expect(trie.getAllWords()).toEqual([
'dog', 'door', 'day', 'cat',
]);
});
});
});
});