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