From a81f6e1d57ee07d8ee1dc6b5abb2f6486cb15505 Mon Sep 17 00:00:00 2001 From: Adrian Mejia Date: Sun, 22 Mar 2020 13:55:10 -0400 Subject: [PATCH] feat(trie): remove method --- src/data-structures/trees/trie-1.js | 48 ++++++++++++++++++++++ src/data-structures/trees/trie.js | 34 ++++++++++++++++ src/data-structures/trees/trie.spec.js | 55 ++++++++++++++++++++++++++ 3 files changed, 137 insertions(+) diff --git a/src/data-structures/trees/trie-1.js b/src/data-structures/trees/trie-1.js index 80ede3b0..fb6ed1bf 100644 --- a/src/data-structures/trees/trie-1.js +++ b/src/data-structures/trees/trie-1.js @@ -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 diff --git a/src/data-structures/trees/trie.js b/src/data-structures/trees/trie.js index b634443b..1a03c241 100644 --- a/src/data-structures/trees/trie.js +++ b/src/data-structures/trees/trie.js @@ -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. diff --git a/src/data-structures/trees/trie.spec.js b/src/data-structures/trees/trie.spec.js index f72f7adf..fca6588e 100644 --- a/src/data-structures/trees/trie.spec.js +++ b/src/data-structures/trees/trie.spec.js @@ -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); }); @@ -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', + ]); + }); + }); }); });