      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;