Code coverage report for sofiatree/lib/sofia-tree.js

Statements: 18.06% (13 / 72)      Branches: 7.55% (4 / 53)      Functions: 11.11% (1 / 9)      Lines: 18.06% (13 / 72)      Ignored: none     

All files » sofiatree/lib/ » sofia-tree.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212                                                    1 1 1   1 1 1   1             1             1                             1                                                                                                       1                                                           1                                                         1                                                                          
//		SofiaTree 0.0.3
 
//	A kind of *Radix tree* data structure baptized **Sofia Tree** in honour to my 
//	beloved daughter.
//
//	A multinode tree with a level for each letter with fast lookup operations."
//	@see http://en.wikipedia.org/wiki/Radix_tree
 
//	Author: Jaime Agudo <jaime.agudo.lopez@gmail.com>
//
//	Github: https://github.com/jaimeagudo/sofiatree/
//
//	License: MIT
 
 
'use strict'; 
// SofiaTree
// ---------
// Tree constructor takes @param `options`. Actually `useCache`, a `boolean`
// to indicate wether caches whole words list of the  subtree below the 
// current node from its root. That way subsequent visits on that node won't 
// need to traverse its subtree. 
 
// This cache config value is  established just on the root node of the tree to 
// avoid redundancy.
 
(function() {
  var root = this;
  var previous_SofiaTree = root.SofiaTree;
 
  Eif( typeof exports !== 'undefined' ) {
    Eif( typeof module !== 'undefined' && module.exports ) {
      exports = module.exports = SofiaTree;
    }
    exports.SofiaTree = SofiaTree;
  } 
  else {
    root.SofiaTree = SofiaTree;
  }
 
 
  SofiaTree.noConflict = function() {
  	root.SofiaTree = previous_SofiaTree;
  	return SofiaTree;
  }
 
 
 
function SofiaTree (options) {
	options = options || {};
 
	this.useCache=!!options.useCache;
};
 
 
// Iterative tree traverse, friendlier with memory compsuntion.
// Passing an empty string as prefix it will return all the words present in the
// whole tree
 
// * `p` prefix above the current node
// * `useCache` configuration value passed along from the root node one
// * @return the `matches` words list 
 
SofiaTree.prototype.__buildWordsArray=function (p, useCache){
 
	var matches=[],
		nodesStack=[],
		prefixesStack=[],
		node,
		prefix;
 
	//Use stack to avoid recursion implementation overhead
	nodesStack.push(this);
	prefixesStack.push(p);
 
	while(nodesStack.length){
 
		prefix=prefixesStack.pop();
		node=nodesStack.pop();
 
		//Recursion base
		if(node.isWord && !node.cache)                      
			matches.push(prefix);
 
	   	if(useCache && node.cache){
			//Shallow copies doesn't work
	   		node.cache.forEach(function(word){	   			
	   			matches.push(word);
	   		});
	   	}else if(node.children){	   	                    
	   	    //Iterate whole subtree building words array and return it
	   		for (var letter in node.children){
	   			nodesStack.push(node.children[letter]);
	   			//A bit redundant, could be optimised
	   			prefixesStack.push(prefix + letter);
	   		};
	   	}
	}
	if(useCache)
		this.cache=matches;
 
	return matches;
}
 
 
// Recursive implementation, to be deprecated
// Passing an empty string as prefix it will return all the words present in the
// whole tree
 
// * `p` prefix above the current node
// * `useCache` configuration value passed along from the root node one
// * `matches` out parameter to return the matches. It reduces stack 
// grow on recursive calls
// * @returns `matches` the same matches word list
 
SofiaTree.prototype.__buildWordsArrayRec=function (p, useCache, matches){
 
	if(this.isWord && !this.cache)
		matches.push(p);
 
   	// Iterate whole subtree building words array and return it
   	if(useCache && this.cache){
		//Shallow copies doesn't work
   		this.cache.forEach(function(word){	   			
   			matches.push(word);
   		});
   	} else if(this.children){
   		for (var letter in this.children){
   			this.children[letter].__buildWordsArrayRec(p + letter, useCache, matches);
   		}
   	}
	if(useCache)
		this.cache=matches;
 
	return matches;
}
 
 
// SofiaTree.getCompletions
// ------------------------
// It returns an array of the words present in the tree that start with the given prefix
// * `prefix` prefix to be completed
// * `recursive` OPTIONAL, defaults to false. To use recursive implementation to traverse the tree and build the result array. 
// * return `matches` the array of words that start with the given prefix
 
SofiaTree.prototype.getCompletions= function(prefix, recursive){
	prefix= typeof prefix == 'string' ? prefix.toLowerCase() : "";
 
	//Tree depth index
    var depth, node;
 
    //Find the root node of the subtree which contains all the completions
    for(depth=0, node=this; depth < prefix.length; depth++){
    	if(!node.children || !node.children[prefix[depth]])
    		return [];
 
    	node = node.children[prefix[depth]];
    } 
		
	return recursive ? node.__buildWordsArrayRec(prefix, this.useCache, []) : 
					   node.__buildWordsArray(prefix, this.useCache);
	
}
 
 
// SofiaTree.insert
// ----------------
// Insert the composing letters of the given word along the tree marking its 
// last node as word. It will insert a lower case version of the given word and
// it won't duplicate words
// 
// * param `word` to be inserted
// * return `this` for chaining convenience
// 
SofiaTree.prototype.insert= function(word){
    var depth;
	var node = this;
 
    // This eliminate empty strings
    if(typeof word != 'string' || !word.length)
    	return;
 
    word=word.toLowerCase();
 
	//Find the last letter of the new word already present in the tree
    for(depth=0; depth < word.length && node.children && node.children[word[depth]]; depth++ ){ 
    	node = node.children[word[depth]]
    }
 
    //Append new nodes for each remaining letter, if necessary. This eliminate duplicates
    if (depth < word.length)
    	while(true){
			// Create a new children property 
			if(! node.children)
				node.children={};
 
			node.children[word[depth]]=new SofiaTree();
 
			//We already inserted the last word letter, make the node as a word
	        if(depth +1 === word.length){
	        	node.children[word[depth]].isWord=true;
	        	break;
	        }
	        node = node.children[word[depth]];
	        depth++;
	    }
	return this;
}
 
 
 
}).call(this);