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.
		
		
		
		
		
			
		
			
				
					
					
						
							478 lines
						
					
					
						
							9.8 KiB
						
					
					
				
			
		
		
	
	
							478 lines
						
					
					
						
							9.8 KiB
						
					
					
				| /**
 | |
|  * Mnemonist TrieMap
 | |
|  * ==================
 | |
|  *
 | |
|  * JavaScript TrieMap 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'),
 | |
|     Iterator = require('obliterator/iterator');
 | |
| 
 | |
| /**
 | |
|  * Constants.
 | |
|  */
 | |
| var SENTINEL = String.fromCharCode(0);
 | |
| 
 | |
| /**
 | |
|  * TrieMap.
 | |
|  *
 | |
|  * @constructor
 | |
|  */
 | |
| function TrieMap(Token) {
 | |
|   this.mode = Token === Array ? 'array' : 'string';
 | |
|   this.clear();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Method used to clear the trie.
 | |
|  *
 | |
|  * @return {undefined}
 | |
|  */
 | |
| TrieMap.prototype.clear = function() {
 | |
| 
 | |
|   // Properties
 | |
|   this.root = {};
 | |
|   this.size = 0;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to set the value of the given prefix in the trie.
 | |
|  *
 | |
|  * @param  {string|array} prefix - Prefix to follow.
 | |
|  * @param  {any}          value  - Value for the prefix.
 | |
|  * @return {TrieMap}
 | |
|  */
 | |
| TrieMap.prototype.set = function(prefix, value) {
 | |
|   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] = value;
 | |
| 
 | |
|   return this;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to update the value of the given prefix in the trie.
 | |
|  *
 | |
|  * @param  {string|array} prefix - Prefix to follow.
 | |
|  * @param  {(oldValue: any | undefined) => any} updateFunction - Update value visitor callback.
 | |
|  * @return {TrieMap}
 | |
|  */
 | |
| TrieMap.prototype.update = function(prefix, updateFunction) {
 | |
|   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] = updateFunction(node[SENTINEL]);
 | |
| 
 | |
|   return this;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to return the value sitting at the end of the given prefix or
 | |
|  * undefined if none exist.
 | |
|  *
 | |
|  * @param  {string|array} prefix - Prefix to follow.
 | |
|  * @return {any|undefined}
 | |
|  */
 | |
| TrieMap.prototype.get = function(prefix) {
 | |
|   var node = this.root,
 | |
|       token,
 | |
|       i,
 | |
|       l;
 | |
| 
 | |
|   for (i = 0, l = prefix.length; i < l; i++) {
 | |
|     token = prefix[i];
 | |
|     node = node[token];
 | |
| 
 | |
|     // Prefix does not exist
 | |
|     if (typeof node === 'undefined')
 | |
|       return;
 | |
|   }
 | |
| 
 | |
|   if (!(SENTINEL in node))
 | |
|     return;
 | |
| 
 | |
|   return node[SENTINEL];
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to delete a prefix from the trie.
 | |
|  *
 | |
|  * @param  {string|array} prefix - Prefix to delete.
 | |
|  * @return {boolean}
 | |
|  */
 | |
| TrieMap.prototype.delete = function(prefix) {
 | |
|   var node = this.root,
 | |
|       toPrune = null,
 | |
|       tokenToPrune = null,
 | |
|       parent,
 | |
|       token,
 | |
|       i,
 | |
|       l;
 | |
| 
 | |
|   for (i = 0, l = prefix.length; i < l; i++) {
 | |
|     token = prefix[i];
 | |
|     parent = node;
 | |
|     node = node[token];
 | |
| 
 | |
|     // Prefix does not exist
 | |
|     if (typeof node === 'undefined')
 | |
|       return false;
 | |
| 
 | |
|     // Keeping track of a potential branch to prune
 | |
|     if (toPrune !== null) {
 | |
|       if (Object.keys(node).length > 1) {
 | |
|         toPrune = null;
 | |
|         tokenToPrune = null;
 | |
|       }
 | |
|     }
 | |
|     else {
 | |
|       if (Object.keys(node).length < 2) {
 | |
|         toPrune = parent;
 | |
|         tokenToPrune = token;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!(SENTINEL in node))
 | |
|     return false;
 | |
| 
 | |
|   this.size--;
 | |
| 
 | |
|   if (toPrune)
 | |
|     delete toPrune[tokenToPrune];
 | |
|   else
 | |
|     delete node[SENTINEL];
 | |
| 
 | |
|   return true;
 | |
| };
 | |
| 
 | |
| // TODO: add #.prune?
 | |
| 
 | |
| /**
 | |
|  * Method used to assert whether the given prefix exists in the TrieMap.
 | |
|  *
 | |
|  * @param  {string|array} prefix - Prefix to check.
 | |
|  * @return {boolean}
 | |
|  */
 | |
| TrieMap.prototype.has = function(prefix) {
 | |
|   var node = this.root,
 | |
|       token;
 | |
| 
 | |
|   for (var i = 0, l = prefix.length; i < l; i++) {
 | |
|     token = prefix[i];
 | |
|     node = node[token];
 | |
| 
 | |
|     if (typeof node === 'undefined')
 | |
|       return false;
 | |
|   }
 | |
| 
 | |
|   return SENTINEL in node;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to retrieve every item in the trie with the given prefix.
 | |
|  *
 | |
|  * @param  {string|array} prefix - Prefix to query.
 | |
|  * @return {array}
 | |
|  */
 | |
| TrieMap.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, node[SENTINEL]]);
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       nodeStack.push(node[k]);
 | |
|       prefixStack.push(isString ? prefix + k : prefix.concat(k));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return matches;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method returning an iterator over the trie's values.
 | |
|  *
 | |
|  * @param  {string|array} [prefix] - Optional starting prefix.
 | |
|  * @return {Iterator}
 | |
|  */
 | |
| TrieMap.prototype.values = function(prefix) {
 | |
|   var node = this.root,
 | |
|       nodeStack = [],
 | |
|       token,
 | |
|       i,
 | |
|       l;
 | |
| 
 | |
|   // Resolving initial prefix
 | |
|   if (prefix) {
 | |
|     for (i = 0, l = prefix.length; i < l; i++) {
 | |
|       token = prefix[i];
 | |
|       node = node[token];
 | |
| 
 | |
|       // If the prefix does not exist, we return an empty iterator
 | |
|       if (typeof node === 'undefined')
 | |
|         return Iterator.empty();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nodeStack.push(node);
 | |
| 
 | |
|   return new Iterator(function() {
 | |
|     var currentNode,
 | |
|         hasValue = false,
 | |
|         k;
 | |
| 
 | |
|     while (nodeStack.length) {
 | |
|       currentNode = nodeStack.pop();
 | |
| 
 | |
|       for (k in currentNode) {
 | |
|         if (k === SENTINEL) {
 | |
|           hasValue = true;
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         nodeStack.push(currentNode[k]);
 | |
|       }
 | |
| 
 | |
|       if (hasValue)
 | |
|         return {done: false, value: currentNode[SENTINEL]};
 | |
|     }
 | |
| 
 | |
|     return {done: true};
 | |
|   });
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method returning an iterator over the trie's prefixes.
 | |
|  *
 | |
|  * @param  {string|array} [prefix] - Optional starting prefix.
 | |
|  * @return {Iterator}
 | |
|  */
 | |
| TrieMap.prototype.prefixes = function(prefix) {
 | |
|   var node = this.root,
 | |
|       nodeStack = [],
 | |
|       prefixStack = [],
 | |
|       token,
 | |
|       i,
 | |
|       l;
 | |
| 
 | |
|   var isString = this.mode === 'string';
 | |
| 
 | |
|   // Resolving initial prefix
 | |
|   if (prefix) {
 | |
|     for (i = 0, l = prefix.length; i < l; i++) {
 | |
|       token = prefix[i];
 | |
|       node = node[token];
 | |
| 
 | |
|       // If the prefix does not exist, we return an empty iterator
 | |
|       if (typeof node === 'undefined')
 | |
|         return Iterator.empty();
 | |
|     }
 | |
|   }
 | |
|   else {
 | |
|     prefix = isString ? '' : [];
 | |
|   }
 | |
| 
 | |
|   nodeStack.push(node);
 | |
|   prefixStack.push(prefix);
 | |
| 
 | |
|   return new Iterator(function() {
 | |
|     var currentNode,
 | |
|         currentPrefix,
 | |
|         hasValue = false,
 | |
|         k;
 | |
| 
 | |
|     while (nodeStack.length) {
 | |
|       currentNode = nodeStack.pop();
 | |
|       currentPrefix = prefixStack.pop();
 | |
| 
 | |
|       for (k in currentNode) {
 | |
|         if (k === SENTINEL) {
 | |
|           hasValue = true;
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         nodeStack.push(currentNode[k]);
 | |
|         prefixStack.push(isString ? currentPrefix + k : currentPrefix.concat(k));
 | |
|       }
 | |
| 
 | |
|       if (hasValue)
 | |
|         return {done: false, value: currentPrefix};
 | |
|     }
 | |
| 
 | |
|     return {done: true};
 | |
|   });
 | |
| };
 | |
| TrieMap.prototype.keys = TrieMap.prototype.prefixes;
 | |
| 
 | |
| /**
 | |
|  * Method returning an iterator over the trie's entries.
 | |
|  *
 | |
|  * @param  {string|array} [prefix] - Optional starting prefix.
 | |
|  * @return {Iterator}
 | |
|  */
 | |
| TrieMap.prototype.entries = function(prefix) {
 | |
|   var node = this.root,
 | |
|       nodeStack = [],
 | |
|       prefixStack = [],
 | |
|       token,
 | |
|       i,
 | |
|       l;
 | |
| 
 | |
|   var isString = this.mode === 'string';
 | |
| 
 | |
|   // Resolving initial prefix
 | |
|   if (prefix) {
 | |
|     for (i = 0, l = prefix.length; i < l; i++) {
 | |
|       token = prefix[i];
 | |
|       node = node[token];
 | |
| 
 | |
|       // If the prefix does not exist, we return an empty iterator
 | |
|       if (typeof node === 'undefined')
 | |
|         return Iterator.empty();
 | |
|     }
 | |
|   }
 | |
|   else {
 | |
|     prefix = isString ? '' : [];
 | |
|   }
 | |
| 
 | |
|   nodeStack.push(node);
 | |
|   prefixStack.push(prefix);
 | |
| 
 | |
|   return new Iterator(function() {
 | |
|     var currentNode,
 | |
|         currentPrefix,
 | |
|         hasValue = false,
 | |
|         k;
 | |
| 
 | |
|     while (nodeStack.length) {
 | |
|       currentNode = nodeStack.pop();
 | |
|       currentPrefix = prefixStack.pop();
 | |
| 
 | |
|       for (k in currentNode) {
 | |
|         if (k === SENTINEL) {
 | |
|           hasValue = true;
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         nodeStack.push(currentNode[k]);
 | |
|         prefixStack.push(isString ? currentPrefix + k : currentPrefix.concat(k));
 | |
|       }
 | |
| 
 | |
|       if (hasValue)
 | |
|         return {done: false, value: [currentPrefix, currentNode[SENTINEL]]};
 | |
|     }
 | |
| 
 | |
|     return {done: true};
 | |
|   });
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Attaching the #.entries method to Symbol.iterator if possible.
 | |
|  */
 | |
| if (typeof Symbol !== 'undefined')
 | |
|   TrieMap.prototype[Symbol.iterator] = TrieMap.prototype.entries;
 | |
| 
 | |
| /**
 | |
|  * Convenience known methods.
 | |
|  */
 | |
| TrieMap.prototype.inspect = function() {
 | |
|   var proxy = new Array(this.size);
 | |
| 
 | |
|   var iterator = this.entries(),
 | |
|       step,
 | |
|       i = 0;
 | |
| 
 | |
|   while ((step = iterator.next(), !step.done))
 | |
|     proxy[i++] = step.value;
 | |
| 
 | |
|   // Trick so that node displays the name of the constructor
 | |
|   Object.defineProperty(proxy, 'constructor', {
 | |
|     value: TrieMap,
 | |
|     enumerable: false
 | |
|   });
 | |
| 
 | |
|   return proxy;
 | |
| };
 | |
| 
 | |
| if (typeof Symbol !== 'undefined')
 | |
|   TrieMap.prototype[Symbol.for('nodejs.util.inspect.custom')] = TrieMap.prototype.inspect;
 | |
| 
 | |
| TrieMap.prototype.toJSON = function() {
 | |
|   return this.root;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Static @.from function taking an arbitrary iterable & converting it into
 | |
|  * a trie.
 | |
|  *
 | |
|  * @param  {Iterable} iterable   - Target iterable.
 | |
|  * @return {TrieMap}
 | |
|  */
 | |
| TrieMap.from = function(iterable) {
 | |
|   var trie = new TrieMap();
 | |
| 
 | |
|   forEach(iterable, function(value, key) {
 | |
|     trie.set(key, value);
 | |
|   });
 | |
| 
 | |
|   return trie;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Exporting.
 | |
|  */
 | |
| TrieMap.SENTINEL = SENTINEL;
 | |
| module.exports = TrieMap;
 |