class TrieNode { constructor(key, parent) { this.key = key this.count = 0 this.children = Object.create(null) if (parent === undefined) { this.parent = null } else { this.parent = parent } } } class Trie { constructor() { // create only root with null key and parent this.root = new TrieNode(null, null) } // Recursively finds the occurrence of all words in a given node static findAllWords(root, word, output) { if (root === null) return if (root.count > 0) { if (typeof output === 'object') { output.push({ word, count: root.count }) } } let key for (key in root.children) { word += key this.findAllWords(root.children[key], word, output) word = word.slice(0, -1) } } insert(word) { if (typeof word !== 'string') return if (word === '') { this.root.count += 1 return } let node = this.root const len = word.length let i for (i = 0; i < len; i++) { if (node.children[word.charAt(i)] === undefined) { node.children[word.charAt(i)] = new TrieNode(word.charAt(i), node) } node = node.children[word.charAt(i)] } node.count += 1 } findPrefix(word) { if (typeof word !== 'string') return null let node = this.root const len = word.length let i // After end of this loop node will be at desired prefix for (i = 0; i < len; i++) { if (node.children[word.charAt(i)] === undefined) return null // No such prefix exists node = node.children[word.charAt(i)] } return node } remove(word, count) { if (typeof word !== 'string') return if (typeof count !== 'number') count = 1 else if (count <= 0) return // for empty string just delete count of root if (word === '') { if (this.root.count >= count) this.root.count -= count else this.root.count = 0 return } let child = this.root const len = word.length let i, key // child: node which is to be deleted for (i = 0; i < len; i++) { key = word.charAt(i) if (child.children[key] === undefined) return child = child.children[key] } // Delete no of occurrences specified if (child.count >= count) child.count -= count else child.count = 0 // If some occurrences are left we don't delete it or else // if the object forms some other objects prefix we don't delete it // For checking an empty object // https://stackoverflow.com/questions/679915/how-do-i-test-for-an-empty-javascript-object if ( child.count <= 0 && Object.keys(child.children).length && child.children.constructor === Object ) { child.parent.children[child.key] = undefined } } findAllWords(prefix) { const output = [] // find the node with provided prefix const node = this.findPrefix(prefix) // No such prefix exists if (node === null) return output Trie.findAllWords(node, prefix, output) return output } contains(word) { // find the node with given prefix const node = this.findPrefix(word) // No such word exists return node !== null && node.count !== 0 } findOccurrences(word) { // find the node with given prefix const node = this.findPrefix(word) // No such word exists if (node === null) return 0 return node.count } } export { Trie }