UNPKG

data-structures

Version:

JavaScript data structures written in CoffeeScript.

184 lines (165 loc) 5.3 kB
### Good for fast insertion/removal/lookup of strings. ## Overview example: ```js var trie = new Trie(['bear', 'beer']); trie.add('hello'); // => 'hello' trie.add('helloha!'); // => 'helloha!' trie.has('bears'); // => false trie.longestPrefixOf('beatrice'); // => 'bea' trie.wordsWithPrefix('hel'); // => ['hello', 'helloha!'] trie.remove('beers'); // => undefined. 'beer' still exists trie.remove('Beer') // => undefined. Case-sensitive trie.remove('beer') // => 'beer'. Removed ``` ## Properties: - size: The total number of words. ### # Needed for breath-first traversal for finding all words with a certain prefix. Queue = require './Queue' # Special value for marking end of word. No conflict possible, since every # node's one letter. WORD_END = 'end' class Trie constructor: (words = []) -> ### Pass an optional array of strings to be inserted initially. ### # Example structure for 'he', 'hello', 'za' # h: # e: # end: true, # l: # l: # o: # end: true, # z: # a: # end: true @_root = {} @size = 0 @add word for word in words add: (word) -> ### Add a whole string to the trie. _Returns:_ the word added. Will return undefined (without adding the value) if the word passed is null or undefined. ### if not word? then return @size++ currentNode = @_root for letter in word if not currentNode[letter]? then currentNode[letter] = {} currentNode = currentNode[letter] # Add the terminator. The value should be something defined, as to not cause # search problems and so that we can validate using `if # currentNode[WORD_END]` rather than `if currentNode[WORD_END]?` currentNode[WORD_END] = yes return word has: (word) -> ### __Returns:_ true or false. ### if not word? then return no currentNode = @_root for letter in word if not currentNode[letter]? then return no currentNode = currentNode[letter] # Check for null terminator if currentNode[WORD_END] then yes else no longestPrefixOf: (word) -> ### Find all words containing the prefix. The word itself counts as a prefix. ```js var trie = new Trie; trie.add('hello'); trie.longestPrefixOf('he'); // 'he' trie.longestPrefixOf('hello'); // 'hello' trie.longestPrefixOf('helloha!'); // 'hello' ``` _Returns:_ the prefix string, or empty string if no prefix found. ### if not word? then return '' currentNode = @_root prefix = '' for letter in word if not currentNode[letter]? then break prefix += letter currentNode = currentNode[letter] return prefix wordsWithPrefix: (prefix) -> ### Find all words containing the prefix. The word itself counts as a prefix. **Watch out for edge cases.** ```js var trie = new Trie; trie.wordsWithPrefix(''); // []. Check later case below. trie.add(''); trie.wordsWithPrefix(''); // [''] trie.add('he'); trie.add('hello'); trie.add('hell'); trie.add('bear'); trie.add('z'); trie.add('zebra'); trie.wordsWithPrefix('hel'); // ['hell', 'hello'] ``` _Returns:_ an array of strings, or empty array if no word found. ### if not prefix? then return [] # Can't set default value to parameter. It turns undefined and null into ''. prefix? or prefix = '' words = [] currentNode = @_root for letter in prefix currentNode = currentNode[letter] if not currentNode? then return [] # Reached the end of prefix. # Enqueue like this: [node, accumulatedLetters] queue = new Queue() queue.enqueue [currentNode, ''] while queue.size isnt 0 [node, accumulatedLetters] = queue.dequeue() if node[WORD_END] words.push prefix + accumulatedLetters for letter, subNode of node queue.enqueue [subNode, accumulatedLetters + letter] return words remove: (word) -> ### _Returns:_ the string removed, or undefined if the word in its whole doesn't exist. **Note:** this means removing `beers` when only `beer` exists will return undefined and conserve `beer`. ### if not word? then return currentNode = @_root prefix = [] for letter in word if not currentNode[letter]? then return currentNode = currentNode[letter] prefix.push [letter, currentNode] # Check for null terminator if not currentNode[WORD_END] then return # Traverse back upward to delete nodes. Last node in prefix is the before- # last letter right now. @size-- delete currentNode[WORD_END] if _hasAtLeastNChildren currentNode, 1 then return word for i in [prefix.length - 1..1] if not _hasAtLeastNChildren prefix[i][1], 1 # previousNode[currentLetter] delete prefix[i - 1][1][prefix[i][0]] else break # Clean the ndoe at the root itself. if not _hasAtLeastNChildren @_root[prefix[0][0]], 1 delete @_root[prefix[0][0]] return word _hasAtLeastNChildren = (node, n) -> return yes if n is 0 childCount = 0 for child of node childCount++ return yes if childCount >= n return no module.exports = Trie