1 var define = require("../define").define, 2 Collection = require("./Collection"), 3 Iterable = require("./Iterable"), 4 base = require("../base"), 5 multiply = base.string.multiply; 6 7 var compare = function(a, b) { 8 var ret = 0; 9 if (a > b) { 10 return 1; 11 } else if (a < b) { 12 return -1; 13 } 14 return ret; 15 16 }; 17 18 var Tree = define([Collection, Iterable], { 19 20 instance : { 21 22 /**@lends comb.collections.Tree.prototype*/ 23 24 /** 25 * Prints a node 26 * @param node node to print 27 * @param level the current level the node is at, Used for formatting 28 */ 29 __printNode : function(node, level) { 30 //console.log(level); 31 var str = []; 32 if (node == null || node == undefined) { 33 str.push(multiply('\t', level)); 34 str.push("~"); 35 console.log(str.join("")); 36 } else { 37 this.__printNode(node.right, level + 1); 38 str.push(multiply('\t', level)); 39 str.push(node.data + "\n"); 40 console.log(str.join("")); 41 this.__printNode(node.left, level + 1); 42 } 43 }, 44 45 /** 46 * Base Class for all tree implementations 47 * @constructs 48 * @augments comb.collections.Collection 49 * @augments comb.collections.Iterable 50 * @memberOf comb.collections 51 * 52 * @param {Object} options options to initialize the tree 53 * @param {Function} options.compare function used to compare items in a tree must return an integer 54 * <ul> 55 * </li>-1 for less than</li> 56 * </li>0 for equal</li> 57 * </li>1 for greater than</li> 58 * </ul> 59 * 60 */ 61 constructor : function(options) { 62 options = options || {}; 63 this.compare = options.compare || compare; 64 this.__root = null; 65 }, 66 67 /** 68 * Inserts an item into the tree 69 * @param {*} data the item to insert 70 */ 71 insert : function(data) { 72 throw new Error("Not Implemented"); 73 }, 74 75 /** 76 * Removes an item from the tree 77 * @param {*} data the item to insert 78 */ 79 remove : function(data) { 80 throw new Error("Not Implemented"); 81 }, 82 83 /** 84 * Clear all items from a tree 85 */ 86 clear : function() { 87 this.__root = null; 88 }, 89 90 /** 91 * Test if a tree is empty 92 * 93 * @return {Boolean} true if empty false otherwise 94 */ 95 isEmpty : function() { 96 return this.__root == null; 97 }, 98 99 /** 100 * Traverse a tree until the callback function returns false 101 * 102 * <p><b>Not typically used directly</b></p> 103 * 104 * @param {Object} node the node to start at 105 * @param {Tree.PRE_ORDER|Tree.POST_ORDER|Tree.IN_ORDER|Tree.REVERSE_ORDER} [order=Tree.IN_ORDER] the traversal scheme 106 * @param {Function} callback called for each item, traversal continues until the function returns false 107 * 108 */ 109 traverseWithCondition : function(node, order, callback) { 110 var cont = true; 111 if (node) { 112 order = order || Tree.PRE_ORDER; 113 if (order === Tree.PRE_ORDER) { 114 cont = callback(node.data); 115 if (cont) { 116 cont = this.traverseWithCondition(node.left, order, callback); 117 cont && (cont = this.traverseWithCondition(node.right, order, callback)); 118 119 } 120 } else if (order === Tree.IN_ORDER) { 121 cont = this.traverseWithCondition(node.left, order, callback); 122 if (cont) { 123 cont = callback(node.data); 124 cont && (cont = this.traverseWithCondition(node.right, order, callback)); 125 } 126 } else if (order === Tree.POST_ORDER) { 127 cont = this.traverseWithCondition(node.left, order, callback); 128 if (cont) { 129 cont && (cont = this.traverseWithCondition(node.right, order, callback)); 130 cont && (cont = callback(node.data)); 131 } 132 } else if (order === Tree.REVERSE_ORDER) { 133 cont = this.traverseWithCondition(node.right, order, callback); 134 if (cont) { 135 cont = callback(node.data); 136 cont && (cont = this.traverseWithCondition(node.left, order, callback)); 137 } 138 } 139 } 140 return cont; 141 }, 142 143 /** 144 * Traverse a tree 145 * 146 * <p><b>Not typically used directly</b></p> 147 * 148 * @param {Object} node the node to start at 149 * @param {Tree.PRE_ORDER|Tree.POST_ORDER|Tree.IN_ORDER|Tree.REVERSE_ORDER} [order=Tree.IN_ORDER] the traversal scheme 150 * @param {Function} callback called for each item 151 * 152 */ 153 traverse : function(node, order, callback) { 154 if (node) { 155 order = order || Tree.PRE_ORDER; 156 if (order === Tree.PRE_ORDER) { 157 callback(node.data); 158 this.traverse(node.left, order, callback); 159 this.traverse(node.right, order, callback); 160 } else if (order === Tree.IN_ORDER) { 161 this.traverse(node.left, order, callback); 162 callback(node.data); 163 this.traverse(node.right, order, callback); 164 } else if (order === Tree.POST_ORDER) { 165 this.traverse(node.left, order, callback); 166 this.traverse(node.right, order, callback); 167 callback(node.data); 168 } else if (order === Tree.REVERSE_ORDER) { 169 this.traverseWithCondition(node.right, order, callback); 170 callback(node.data); 171 this.traverseWithCondition(node.left, order, callback); 172 173 } 174 } 175 }, 176 177 /** 178 * Loop through each item in the tree 179 * @param {Function} cb called for each item in the tree 180 * @param {Object} [scope=this] scope to call the function in 181 * @param {Tree.PRE_ORDER|Tree.POST_ORDER|Tree.IN_ORDER|Tree.REVERSE_ORDER} [order=Tree.IN_ORDER] the traversal scheme 182 */ 183 forEach : function(cb, scope, order) { 184 if (typeof cb !== "function") 185 throw new TypeError(); 186 order = order || Tree.IN_ORDER; 187 scope = scope || this; 188 this.traverse(this.__root, order, function(node) { 189 cb.call(scope, node, this); 190 }); 191 }, 192 193 /** 194 * Loop through each item in the tree, collecting the value returned by the callback funciton. 195 * @param {Function} cb called for each item in the tree. 196 * Whatever the function returns is inserted into the return tree 197 * @param {Object} [scope=this] scope to call the function in 198 * @param {Tree.PRE_ORDER|Tree.POST_ORDER|Tree.IN_ORDER|Tree.REVERSE_ORDER} [order=Tree.IN_ORDER] the traversal scheme 199 * 200 * @return {comb.collections.Tree} the tree with the mapped items 201 */ 202 map : function(cb, scope, order) { 203 if (typeof cb !== "function") 204 throw new TypeError(); 205 206 order = order || Tree.IN_ORDER; 207 scope = scope || this; 208 var construct = this.constructor; 209 var ret = new construct(); 210 this.traverse(this.__root, order, function(node) { 211 ret.insert(cb.call(scope, node, this)); 212 }); 213 return ret; 214 }, 215 216 /** 217 * Filters a tree, only returning items that result in true being returned from the callback 218 * 219 * @param {Function} cb called for each item in the tree 220 * @param {Object} [scope=this] scope to call the function in 221 * @param {Tree.PRE_ORDER|Tree.POST_ORDER|Tree.IN_ORDER|Tree.REVERSE_ORDER} [order=Tree.IN_ORDER] the traversal scheme 222 * 223 * @return {comb.collections.Tree} the tree with items that resulted in true being returned from the callback 224 */ 225 filter : function(cb, scope, order) { 226 if (typeof cb !== "function") 227 throw new TypeError(); 228 229 order = order || Tree.IN_ORDER; 230 scope = scope || this; 231 var construct = this.constructor; 232 var ret = new construct(); 233 this.traverse(this.__root, order, function(node) { 234 var include = cb.call(scope, node, this); 235 include && ret.insert(node); 236 }); 237 return ret; 238 }, 239 240 /** 241 * Reduces a tree 242 * 243 * @param {Function} cb called for each item in the tree 244 * @param [accumulator=First item in tree(Order dependant)] scope to call the function in 245 * @param {Tree.PRE_ORDER|Tree.POST_ORDER|Tree.IN_ORDER|Tree.REVERSE_ORDER} [order=Tree.IN_ORDER] the traversal scheme 246 * 247 * @return the result of the reduce function 248 */ 249 reduce : function(fun, accumulator, order) { 250 var arr = this.toArray(order); 251 var args = [fun]; 252 if (!base.isUndefined(accumulator) && !base.isNull(accumulator)) { 253 args.push(accumulator); 254 } 255 return arr.reduce.apply(arr, args); 256 }, 257 258 /** 259 * Reduces from right to left 260 * 261 * @param {Function} cb called for each item in the tree 262 * @param [accumulator=First item in tree(Order dependant)] scope to call the function in 263 * @param {Tree.PRE_ORDER|Tree.POST_ORDER|Tree.IN_ORDER|Tree.REVERSE_ORDER} [order=Tree.IN_ORDER] the traversal scheme 264 * 265 * @return the result of the reduce function 266 */ 267 reduceRight : function(fun, accumulator, order) { 268 var arr = this.toArray(order); 269 var args = [fun]; 270 if (!base.isUndefined(accumulator) && !base.isNull(accumulator)) { 271 args.push(accumulator); 272 } 273 return arr.reduceRight.apply(arr, args); 274 }, 275 276 /** 277 * Determines if every item meets the condition returned by the callback. 278 * 279 * @param {Function} cb called for each item in the tree 280 * @param {Object} [scope=this] scope to call the function in 281 * @param {Tree.PRE_ORDER|Tree.POST_ORDER|Tree.IN_ORDER|Tree.REVERSE_ORDER} [order=Tree.IN_ORDER] the traversal scheme 282 * 283 * @return {Boolean} True if every item passed false otherwise 284 */ 285 every : function(cb, scope, order) { 286 if (typeof cb !== "function") 287 throw new TypeError(); 288 289 order = order || Tree.IN_ORDER; 290 scope = scope || this; 291 var ret = false; 292 this.traverseWithCondition(this.__root, order, function(node) { 293 return (ret = cb.call(scope, node, this)); 294 }); 295 return ret; 296 }, 297 298 /** 299 * Determines if some item meet the condition returned by the callback. Traversal ends the first time true is found. 300 * 301 * @param {Function} cb called for each item in the tree 302 * @param {Object} [scope=this] scope to call the function in 303 * @param {Tree.PRE_ORDER|Tree.POST_ORDER|Tree.IN_ORDER|Tree.REVERSE_ORDER} [order=Tree.IN_ORDER] the traversal scheme 304 * 305 * @return {Boolean} True if every item passed false otherwise 306 */ 307 some : function(cb, scope, order) { 308 if (typeof cb !== "function") 309 throw new TypeError(); 310 311 order = order || Tree.IN_ORDER; 312 scope = scope || this; 313 var ret; 314 this.traverseWithCondition(this.__root, order, function(node) { 315 ret = cb.call(scope, node, this); 316 return !ret; 317 }); 318 return ret; 319 }, 320 321 /** 322 * Converts a tree into an array based on the specified order 323 * 324 * @param {Tree.PRE_ORDER|Tree.POST_ORDER|Tree.IN_ORDER|Tree.REVERSE_ORDER} [order=Tree.IN_ORDER] the traversal scheme 325 * 326 * @return {Array} array of all items in the order specified. 327 */ 328 toArray : function(order) { 329 order = order || Tree.IN_ORDER; 330 var arr = []; 331 this.traverse(this.__root, order, function(node) { 332 arr.push(node); 333 }); 334 return arr; 335 }, 336 337 338 339 /** 340 * Determines if a value is contained in the tree 341 * @param {*} value the value to find 342 * 343 * @return {Boolean} true if the tree contains the item false otherwise. 344 */ 345 contains : function(value) { 346 var ret = false; 347 var root = this.__root; 348 while (root != null) { 349 var cmp = this.compare(value, root.data); 350 if (cmp) { 351 root = root[(cmp == -1) ? "left" : "right"]; 352 } else { 353 ret = true; 354 root = null; 355 } 356 } 357 return ret; 358 }, 359 360 /** 361 * Finds a value in the tree 362 * @param {*} value the value to find 363 * 364 * @return the value of the node that matched 365 */ 366 find : function(value) { 367 var ret; 368 var root = this.__root; 369 while (root != null) { 370 var cmp = this.compare(value, root.data); 371 if (cmp) { 372 root = root[(cmp == -1) ? "left" : "right"]; 373 } else { 374 ret = root.data; 375 break; 376 } 377 } 378 return ret; 379 }, 380 381 /** 382 * Find all values less than a value 383 * @param {*} value the value to find nodes less than 384 * @param {Boolean} [exclusive=false] if true the value will NOT be included in the return array 385 * 386 * @return {Array} the array containing all values less than 387 */ 388 findLessThan : function(value, exclusive) { 389 //find a better way!!!! 390 var ret = [], compare = this.compare; 391 this.traverseWithCondition(this.__root, exports.IN_ORDER, function(v) { 392 var cmp = compare(value, v); 393 if ((!exclusive && cmp == 0) || cmp == 1) { 394 ret.push(v); 395 return true; 396 } else { 397 return false; 398 } 399 }); 400 return ret; 401 }, 402 403 /** 404 * Find all greater than a value 405 * @param {*} value the value to find nodes greater than 406 * @param {Boolean} [exclusive=false] if true the value will NOT be included in the return array 407 * 408 * @return {Array} the array containing all values greater than 409 */ 410 findGreaterThan : function(value, exclusive) { 411 //find a better way!!!! 412 var ret = [], compare = this.compare; 413 this.traverse(this.__root, exports.REVERSE_ORDER, function(v) { 414 var cmp = compare(value, v); 415 if ((!exclusive && cmp == 0) || cmp == -1) { 416 ret.push(v); 417 return true; 418 } else { 419 return false; 420 } 421 }); 422 return ret; 423 }, 424 425 /** 426 * Prints a tree to the console. 427 */ 428 print : function() { 429 this.__printNode(this.__root, 0); 430 }, 431 432 __iterator__ : function(){ 433 434 } 435 }, 436 437 static : { 438 /** @lends comb.collections.Tree */ 439 440 /** 441 * Pre Order 442 */ 443 PRE_ORDER : "pre_order", 444 445 /** 446 * In Order 447 */ 448 IN_ORDER : "in_order", 449 450 /** 451 * Post Order 452 */ 453 POST_ORDER:"post_order", 454 455 /** 456 457 * Reverse Order 458 */ 459 REVERSE_ORDER : "reverse_order" 460 } 461 }); 462 /**@ignore*/ 463 module.exports = exports = Tree; 464 465