1 var obj = require("./object"), string = require("./string"), date = require("./date"), number = require("./number"), misc = require("./misc");
  2 
  3 /**
  4  *
  5  * Converts an arguments object to an array
  6  * @function
  7  * @param {Arguments} args the arguments object to convert
  8  * @memberOf comb
  9  * @returns {Array} array version of the arguments object
 10  */
 11 var argsToArray = exports.argsToArray = function(args) {
 12     return Array.prototype.slice.call(args);
 13 };
 14 
 15 var isArray = exports.isArray = function(obj) {
 16     return obj && obj instanceof Array;
 17 };
 18 
 19 var cross = function(num, cross) {
 20     var ret = cross.reduceRight(function(a, b) {
 21         if (!isArray(b)) b = [b];
 22         b.unshift(num)
 23         a.unshift(b);
 24         return a;
 25     }, []);
 26     return ret;
 27 };
 28 
 29 var permute = function(num, cross, length) {
 30     var ret = [];
 31     for (var i = 0; i < cross.length; i++) {
 32         ret.push([num].concat(array.rotate(cross, i)).slice(0, length));
 33     }
 34     return ret;
 35 };
 36 
 37 
 38 var intersection = function(a, b) {
 39     var ret = [];
 40     if (isArray(a) && isArray(b)) {
 41         if (a.length && b.length) {
 42             var aOne = a[0];
 43             if (b.indexOf(aOne) != -1) {
 44                 ret = [aOne].concat(intersection(a.slice(1), b));
 45             } else {
 46                 ret = intersection(a.slice(1), b);
 47             }
 48         }
 49     }
 50 
 51     return ret;
 52 };
 53 
 54 var comb = exports, array;
 55 /**
 56  * @namespace Utilities for Arrays
 57  */
 58 comb.array = {
 59 
 60 
 61     /**
 62      * converts anything to an array
 63      *
 64      * @example
 65      *  comb.array.toArray({a : "b", b : "c"}) => [["a","b"], ["b","c"]];
 66      *  comb.array.toArray("a") => ["a"]
 67      *  comb.array.toArray(["a"]) =>  ["a"];
 68      *  comb.array.toArray() => [];
 69      *  comb.array.toArray("a", {a : "b"}) => ["a", ["a", "b"]];
 70      */
 71     toArray : function(o) {
 72         var ret = [];
 73         if (o != null) {
 74             var args = argsToArray(arguments);
 75             if (args.length == 1) {
 76                 if (isArray(o)) {
 77                     ret = o;
 78                 } else if (string.isString(o) || date.isDate(o) || number.isNumber(o) || misc.isBoolean(o)) {
 79                     ret.push(o);
 80                 } else if (obj.isObject(o)) {
 81                     for (var i in o) {
 82                         if (o.hasOwnProperty(i)) {
 83                             ret.push([i, o[i]]);
 84                         }
 85                     }
 86                 }
 87             } else {
 88                 args.forEach(function(a){
 89                     ret = ret.concat(array.toArray(a));
 90                 })
 91             }
 92         }
 93         return ret;
 94     },
 95 
 96     /**
 97      * Sums all items in an array
 98      *
 99      * @example
100      *
101      *  comb.array.sum([1,2,3]) => 6
102      *  comb.array.sum(["A","B","C"]) => "ABC";
103      *  var d1 = new Date(1999), d2 = new Date(2000), d3 = new Date(3000);
104      *  comb.array.sum([d1,d2,d3]) => "Wed Dec 31 1969 18:00:01 GMT-0600 (CST)"
105      *                              + "Wed Dec 31 1969"  18:00:02 GMT-0600 (CST)"
106      *                              + "Wed Dec 31 1969 18:00:03 GMT-0600 (CST)"
107      *  comb.array.sum([{},{},{}]) => "[object Object][object Object][object Object]";
108      *
109      * @param {Number[]} array the array of numbers to sum
110      */
111     sum : function(array) {
112         array = array || [];
113         if (array.length) {
114             return array.reduce(function(a, b) {
115                 return a + b;
116             });
117         } else {
118             return 0;
119         }
120     },
121 
122 
123     /**
124      * Removes duplicates from an array
125      *
126      * @example
127      *
128      * comb.array.removeDuplicates([1,1,1]) => [1]
129      * comb.array.removeDuplicates([1,2,3,2]) => [1,2,3]
130      *
131      * @param {Aray} array the array of elements to remove duplicates from
132      */
133     removeDuplicates : function(arr) {
134         if (isArray(arr)) {
135             var ret = arr.reduce(function(a, b) {
136                 if (a.indexOf(b) == -1) {
137                     return a.concat(b);
138                 } else {
139                     return a;
140                 }
141             }, []);
142             return ret;
143         }
144     },
145 
146     /**
147      * Rotates an array the number of specified positions
148      *
149      * @example
150      * var arr = ["a", "b", "c", "d"];
151      * comb.array.rotate(arr)     => ["b", "c", "d", "a"]
152      * comb.array.rotate(arr, 2)  => ["c", "d", "a", "b"]);
153      * comb.array.rotate(arr, 3)  => ["d", "a", "b", "c"]);
154      * comb.array.rotate(arr, 4)  => ["a", "b", "c", "d"]);
155      * comb.array.rotate(arr, -1) => ["d", "a", "b", "c"]);
156      * comb.array.rotate(arr, -2) => ["c", "d", "a", "b"]);
157      * comb.array.rotate(arr, -3) => ["b", "c", "d", "a"]);
158      * comb.array.rotate(arr, -4) => ["a", "b", "c", "d"]);
159      *
160      * @param {Array} array the array of elements to remove duplicates from
161      * @param {Number} numberOfTimes the number of times to rotate the array
162      */
163     rotate : function(arr, numberOfTimes) {
164         var ret = arr.slice();
165         if (typeof numberOfTimes != "number") {
166             numberOfTimes = 1;
167         }
168         if (numberOfTimes && isArray(arr)) {
169             if (numberOfTimes > 0) {
170                 ret.push(ret.shift());
171                 numberOfTimes--;
172             } else {
173                 ret.unshift(ret.pop());
174                 numberOfTimes++;
175             }
176             return array.rotate(ret, numberOfTimes);
177         } else {
178             return ret;
179         }
180     },
181 
182     /**
183      * Finds all permutations of an array
184      *
185      * @example
186      * var arr = [1,2,3];
187      * comb.array.permutations(arr)    => [[ 1, 2, 3 ],[ 1, 3, 2 ],[ 2, 3, 1 ],
188      *                                     [ 2, 1, 3 ],[ 3, 1, 2 ],[ 3, 2, 1 ]]
189      * comb.array.permutations(arr, 2) => [[ 1, 2],[ 1, 3],[ 2, 3],[ 2, 1],[ 3, 1],[ 3, 2]]
190      * comb.array.permutations(arr, 1) => [[1],[2],[3]]
191      * comb.array.permutations(arr, 0) => [[]]
192      * comb.array.permutations(arr, 4) => []
193      *
194      * @param {Array} arr the array to permute.
195      * @param {Number} length the number of elements to permute.
196      */
197     permutations : function(arr, length) {
198         var ret = [];
199         if (isArray(arr)) {
200             var copy = arr.slice(0);
201             if (typeof length != "number") {
202                 length = arr.length;
203             }
204             if (!length) {
205                 ret = [
206                     []
207                 ];
208             } else if (length <= arr.length) {
209                 ret = arr.reduce(function(a, b, i) {
210                     if (length > 1) {
211                         var ret = permute(b, array.rotate(copy, i).slice(1), length);
212                     } else {
213                         ret = [
214                             [b]
215                         ];
216                     }
217                     return a.concat(ret);
218                 }, [])
219             }
220         }
221         return ret;
222     },
223 
224     /**
225      * Zips to arrays together
226      *
227      * @example
228      *  var a = [ 4, 5, 6 ], b = [ 7, 8, 9 ]
229      *  comb.array.zip([1], [2], [3]) => [[ 1, 2, 3 ]]);
230      *  comb.array.zip([1,2], [2], [3]) => [[ 1, 2, 3 ],[2, null, null]]
231      *  comb.array.zip([1,2,3], a, b) => [[1, 4, 7],[2, 5, 8],[3, 6, 9]]
232      *  comb.array.zip([1,2], a, b) => [[1, 4, 7],[2, 5, 8]]
233      *  comb.array.zip(a, [1,2], [8]) => [[4,1,8],[5,2,null],[6,null,null]]
234      *
235      * @param arrays variable number of arrays to zip together
236      */
237     zip : function() {
238         var ret = [];
239         var arrs = argsToArray(arguments);
240         if (arrs.length > 1) {
241             arr1 = arrs.shift();
242             if (isArray(arr1)) {
243                 ret = arr1.reduce(function(a, b, i) {
244                     var curr = [b];
245                     for (var j = 0; j < arrs.length; j++) {
246                         var currArr = arrs[j];
247                         if (isArray(currArr) && currArr[i]) {
248                             curr.push(currArr[i]);
249                         } else {
250                             curr.push(null);
251                         }
252                     }
253                     a.push(curr)
254                     return a;
255                 }, []);
256             }
257         }
258         return ret;
259     },
260 
261     /**
262      * Transposes an array of arrays
263      * @example
264      *
265      * comb.array.transpose([[1,2,3], [4,5,6]]) => [ [ 1, 4 ], [ 2, 5 ], [ 3, 6 ] ]
266      * comb.array.transpose([[1,2], [3,4], [5,6]]) => [ [ 1, 3, 5 ], [ 2, 4, 6 ] ]
267      * comb.array.transpose([[1], [3,4], [5,6]]) => [[1]])
268      *
269      * @param [Array[Array[]]] arr Array of arrays
270      */
271     transpose : function(arr) {
272         var ret = [];
273         if (isArray(arr) && arr.length) {
274             var last;
275             arr.forEach(function(a) {
276                 if (isArray(a) && (!last || a.length == last.length)) {
277                     a.forEach(function(b, i) {
278                         !ret[i] && (ret[i] = []);
279                         ret[i].push(b);
280                     });
281                     last = a;
282                 }
283             });
284         }
285         return ret;
286     },
287     /**
288      * Retrieves values at specified indexes in the array
289      *
290      * @example
291      *
292      *  var arr =["a", "b", "c", "d"]
293      *   comb.array.valuesAt(arr, 1,2,3) => ["b", "c", "d"];
294      *   comb.array.valuesAt(arr, 1,2,3, 4) => ["b", "c", "d", null];
295      *   comb.array.valuesAt(arr, 0,3) => ["a", "d"];
296      *
297      *   @param {Array} arr the array to retrieve values from
298      *   @index {Number} index variable number of indexes to retrieve
299      */
300     valuesAt : function(arr, indexes) {
301         var ret = [];
302         var indexes = argsToArray(arguments);
303         var arr = indexes.shift(), l = arr.length;
304         if (isArray(arr) && indexes.length) {
305             for (var i = 0; i < indexes.length; i++) {
306                 ret.push(arr[indexes[i]] || null);
307             }
308         }
309         return ret;
310     },
311 
312     /**
313      * Union a variable number of arrays together
314      *
315      * @example
316      *
317      * comb.array.union(['a','b','c'], ['b','c', 'd']) => ["a", "b", "c", "d"]
318      * comb.array.union(["a"], ["b"], ["c"], ["d"], ["c"]) => ["a", "b", "c", "d"]
319      *
320      * @param arrs variable number of arrays to union
321      */
322     union : function() {
323         var ret = [];
324         var arrs = argsToArray(arguments);
325         if (arrs.length > 1) {
326             ret = array.removeDuplicates(arrs.reduce(function(a, b) {
327                 return a.concat(b);
328             }, []));
329         }
330         return ret;
331     },
332 
333     /**
334      * Finds the intersection of arrays
335      * NOTE : this function accepts an arbitrary number of arrays
336      *
337      * @example
338      * comb.array.intersect([1,2], [2,3], [2,3,5]) => [2]
339      * comb.array.intersect([1,2,3], [2,3,4,5], [2,3,5]) => [2,3]
340      * comb.array.intersect([1,2,3,4], [2,3,4,5], [2,3,4,5]) => [2,3,4]
341      * comb.array.intersect([1,2,3,4,5], [1,2,3,4,5], [1,2,3]) => [1,2,3]
342      * comb.array.intersect([[1,2,3,4,5],[1,2,3,4,5],[1,2,3]]) => [1,2,3]
343      *
344      * @param {Array} a
345      * @param {Array} b
346      */
347     intersect : function(a, b) {
348         var collect = [], set;
349         var args = argsToArray(arguments);
350         if (args.length > 1) {
351             //assume we are intersections all the lists in the array
352             set = args;
353         } else {
354             set = args[0];
355         }
356         if (isArray(set)) {
357             var x = set.shift();
358             var collect = set.reduce(function(a, b) {
359                 return intersection(a, b);
360             }, x);
361         }
362         return array.removeDuplicates(collect);
363     },
364 
365     /**
366      * Finds the powerset of an array
367      *
368      * @example
369      *
370      *  comb.array.powerSet([1,2]) => [
371      *           [],
372      *           [ 1 ],
373      *           [ 2 ],
374      *           [ 1, 2 ]
375      *   ]
376      *   comb.array.powerSet([1,2,3]) => [
377      *           [],
378      *           [ 1 ],
379      *           [ 2 ],
380      *           [ 1, 2 ],
381      *           [ 3 ],
382      *           [ 1, 3 ],
383      *           [ 2, 3 ],
384      *           [ 1, 2, 3 ]
385      *       ]
386      *   comb.array.powerSet([1,2,3,4]) => [
387      *           [],
388      *           [ 1 ],
389      *           [ 2 ],
390      *           [ 1, 2 ],
391      *           [ 3 ],
392      *           [ 1, 3 ],
393      *           [ 2, 3 ],
394      *           [ 1, 2, 3 ],
395      *           [ 4 ],
396      *           [ 1, 4 ],
397      *           [ 2, 4 ],
398      *           [ 1, 2, 4 ],
399      *           [ 3, 4 ],
400      *           [ 1, 3, 4 ],
401      *           [ 2, 3, 4 ],
402      *           [ 1, 2, 3, 4 ]
403      *       ]
404      *
405      * @param {Array} arr the array to find the powerset of
406      */
407     powerSet : function(arr) {
408         var ret = [];
409         if (isArray(arr) && arr.length) {
410             var ret = arr.reduce(function(a, b) {
411                 var ret = a.map(function(c) {
412                     return c.concat(b);
413                 })
414                 return a.concat(ret);
415             }, [
416                 []
417             ]);
418         }
419         return ret;
420     },
421 
422     /**
423      * Find the cartesian product of two arrays
424      *
425      * @example
426      *
427      * comb.array.cartesian([1,2], [2,3]) => [
428      *           [1,2],
429      *           [1,3],
430      *           [2,2],
431      *           [2,3]
432      *       ]
433      * comb.array.cartesian([1,2], [2,3,4]) => [
434      *           [1,2],
435      *           [1,3],
436      *           [1,4] ,
437      *           [2,2],
438      *           [2,3],
439      *           [2,4]
440      *       ]
441      * comb.array.cartesian([1,2,3], [2,3,4]) => [
442      *           [1,2],
443      *           [1,3],
444      *           [1,4] ,
445      *           [2,2],
446      *           [2,3],
447      *           [2,4] ,
448      *           [3,2],
449      *           [3,3],
450      *           [3,4]
451      *       ]
452      *
453      * @param {Array} a
454      * @param {Array} b
455      */
456     cartesian : function(a, b) {
457         var ret = [];
458         if (isArray(a) && isArray(b) && a.length && b.length)
459             ret = cross(a[0], b).concat(array.cartesian(a.slice(1), b));
460         return ret;
461     },
462 
463     /**
464      * Compacts an array removing null or undefined objects from the array.
465      *
466      * @example
467      *
468      * var x;
469      * comb.array.compact([1,null,null,x,2]) => [1,2]
470      * comb.array.compact([1,2]) => [1,2]
471      *
472      * @param {Array} arr
473      */
474     compact : function(arr){
475         var ret = [];
476        if(isArray(arr) && arr.length){
477            ret = arr.filter(function(item){
478                return !misc.isUndefinedOrNull(item);
479            })
480        }
481         return ret;
482     },
483 
484     /**
485      * Flatten multiple arrays into a single array
486      *
487      * @example
488      *
489      * comb.array.flatten([1,2], [2,3], [3,4]) => [1,2,2,3,3,4]
490      * comb.array.flatten([1,"A"], [2,"B"], [3,"C"]) => [1,"A",2,"B",3,"C"]
491      *
492      * @param array
493      */
494     flatten : function(array) {
495         var set;
496         var args = argsToArray(arguments);
497         if (args.length > 1) {
498             //assume we are intersections all the lists in the array
499             set = args;
500         } else {
501             set = array;
502         }
503         return set.reduce(function(a, b) {
504             return a.concat(b);
505         }, []);
506     }
507 
508 };
509 
510 array = comb.array;