      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;