You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					168 lines
				
				3.4 KiB
			
		
		
			
		
	
	
					168 lines
				
				3.4 KiB
			| 
											3 years ago
										 | /** | ||
|  |  * Mnemonist Trie | ||
|  |  * =============== | ||
|  |  * | ||
|  |  * JavaScript Trie implementation based upon plain objects. As such this | ||
|  |  * structure is more a convenience building upon the trie's advantages than | ||
|  |  * a real performant alternative to already existing structures. | ||
|  |  * | ||
|  |  * Note that the Trie is based upon the TrieMap since the underlying machine | ||
|  |  * is the very same. The Trie just does not let you set values and only | ||
|  |  * considers the existence of the given prefixes. | ||
|  |  */ | ||
|  | var forEach = require('obliterator/foreach'), | ||
|  |     TrieMap = require('./trie-map.js'); | ||
|  | 
 | ||
|  | /** | ||
|  |  * Constants. | ||
|  |  */ | ||
|  | var SENTINEL = String.fromCharCode(0); | ||
|  | 
 | ||
|  | /** | ||
|  |  * Trie. | ||
|  |  * | ||
|  |  * @constructor | ||
|  |  */ | ||
|  | function Trie(Token) { | ||
|  |   this.mode = Token === Array ? 'array' : 'string'; | ||
|  |   this.clear(); | ||
|  | } | ||
|  | 
 | ||
|  | // Re-using TrieMap's prototype
 | ||
|  | for (var methodName in TrieMap.prototype) | ||
|  |   Trie.prototype[methodName] = TrieMap.prototype[methodName]; | ||
|  | 
 | ||
|  | // Dropping irrelevant methods
 | ||
|  | delete Trie.prototype.set; | ||
|  | delete Trie.prototype.get; | ||
|  | delete Trie.prototype.values; | ||
|  | delete Trie.prototype.entries; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to add the given prefix to the trie. | ||
|  |  * | ||
|  |  * @param  {string|array} prefix - Prefix to follow. | ||
|  |  * @return {TrieMap} | ||
|  |  */ | ||
|  | Trie.prototype.add = function(prefix) { | ||
|  |   var node = this.root, | ||
|  |       token; | ||
|  | 
 | ||
|  |   for (var i = 0, l = prefix.length; i < l; i++) { | ||
|  |     token = prefix[i]; | ||
|  | 
 | ||
|  |     node = node[token] || (node[token] = {}); | ||
|  |   } | ||
|  | 
 | ||
|  |   // Do we need to increase size?
 | ||
|  |   if (!(SENTINEL in node)) | ||
|  |     this.size++; | ||
|  | 
 | ||
|  |   node[SENTINEL] = true; | ||
|  | 
 | ||
|  |   return this; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to retrieve every item in the trie with the given prefix. | ||
|  |  * | ||
|  |  * @param  {string|array} prefix - Prefix to query. | ||
|  |  * @return {array} | ||
|  |  */ | ||
|  | Trie.prototype.find = function(prefix) { | ||
|  |   var isString = typeof prefix === 'string'; | ||
|  | 
 | ||
|  |   var node = this.root, | ||
|  |       matches = [], | ||
|  |       token, | ||
|  |       i, | ||
|  |       l; | ||
|  | 
 | ||
|  |   for (i = 0, l = prefix.length; i < l; i++) { | ||
|  |     token = prefix[i]; | ||
|  |     node = node[token]; | ||
|  | 
 | ||
|  |     if (typeof node === 'undefined') | ||
|  |       return matches; | ||
|  |   } | ||
|  | 
 | ||
|  |   // Performing DFS from prefix
 | ||
|  |   var nodeStack = [node], | ||
|  |       prefixStack = [prefix], | ||
|  |       k; | ||
|  | 
 | ||
|  |   while (nodeStack.length) { | ||
|  |     prefix = prefixStack.pop(); | ||
|  |     node = nodeStack.pop(); | ||
|  | 
 | ||
|  |     for (k in node) { | ||
|  |       if (k === SENTINEL) { | ||
|  |         matches.push(prefix); | ||
|  |         continue; | ||
|  |       } | ||
|  | 
 | ||
|  |       nodeStack.push(node[k]); | ||
|  |       prefixStack.push(isString ? prefix + k : prefix.concat(k)); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   return matches; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Attaching the #.keys method to Symbol.iterator if possible. | ||
|  |  */ | ||
|  | if (typeof Symbol !== 'undefined') | ||
|  |   Trie.prototype[Symbol.iterator] = Trie.prototype.keys; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Convenience known methods. | ||
|  |  */ | ||
|  | Trie.prototype.inspect = function() { | ||
|  |   var proxy = new Set(); | ||
|  | 
 | ||
|  |   var iterator = this.keys(), | ||
|  |       step; | ||
|  | 
 | ||
|  |   while ((step = iterator.next(), !step.done)) | ||
|  |     proxy.add(step.value); | ||
|  | 
 | ||
|  |   // Trick so that node displays the name of the constructor
 | ||
|  |   Object.defineProperty(proxy, 'constructor', { | ||
|  |     value: Trie, | ||
|  |     enumerable: false | ||
|  |   }); | ||
|  | 
 | ||
|  |   return proxy; | ||
|  | }; | ||
|  | 
 | ||
|  | if (typeof Symbol !== 'undefined') | ||
|  |   Trie.prototype[Symbol.for('nodejs.util.inspect.custom')] = Trie.prototype.inspect; | ||
|  | 
 | ||
|  | Trie.prototype.toJSON = function() { | ||
|  |   return this.root; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Static @.from function taking an arbitrary iterable & converting it into | ||
|  |  * a trie. | ||
|  |  * | ||
|  |  * @param  {Iterable} iterable   - Target iterable. | ||
|  |  * @return {Trie} | ||
|  |  */ | ||
|  | Trie.from = function(iterable) { | ||
|  |   var trie = new Trie(); | ||
|  | 
 | ||
|  |   forEach(iterable, function(value) { | ||
|  |     trie.add(value); | ||
|  |   }); | ||
|  | 
 | ||
|  |   return trie; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Exporting. | ||
|  |  */ | ||
|  | Trie.SENTINEL = SENTINEL; | ||
|  | module.exports = Trie; |