js数组工具(交集|并集|补集|差集|求和|分组)

662 阅读2分钟

这是我参与更文挑战的第1天,活动详情查看: 更文挑战

准备. 此工具是对数组元素的操作, 因此先准备针对元素操作的工具.

/**
 * js compare
 * <p><i>注意: deepCompare({0:'a', length:1, __proto__:Array(0)}, ['a']) 在Chrome中也会返回true.</i></p>
 * @example
 * console.assert(deepCompare({0:'a'}, {0:'a'}), '粗大事儿了!');
 *
 * @return {boolean}
 */
function deepCompare() {
    var args = arguments, l = args.length;
    if (l < 2) {
        return true;
        // throw "Need two or more arguments to compare";
    }
    var arg0 = args[0];
    var i, leftChain, rightChain;

    var compare2Objects = function (x, y) {
        var p;
        // remember that NaN === NaN returns false
        // and isNaN(undefined) returns true
        if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
            return true;
        }

        // Compare primitives and functions.
        // Check if both arguments link to the same object.
        // Especially useful on the step where we compare prototypes
        if (x === y) {
            return true;
        }

        // Works in case when functions are created in constructor.
        // Comparing dates is a common scenario. Another built-ins?
        // We can even handle functions passed across iframes
        if ((typeof x === 'function' && typeof y === 'function') ||
            (x instanceof Date && y instanceof Date) ||
            (x instanceof RegExp && y instanceof RegExp) ||
            (x instanceof String && y instanceof String) ||
            (x instanceof Number && y instanceof Number)) {
            return x.toString() === y.toString();
        }

        // At last checking prototypes as good as we can
        if (!(x instanceof Object && y instanceof Object)) {
            return false;
        }

        if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
            return false;
        }

        if (x.constructor !== y.constructor) {
            return false;
        }

        if (x.prototype !== y.prototype) {
            return false;
        }

        // Check for infinitive linking loops
        if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
            return false;
        }

        // Quick checking of one object being a subset of another.
        for (p in y) {
            if (y.hasOwnProperty(p) ^ x.hasOwnProperty(p)) {
                return false;
            } else if (typeof y[p] !== typeof x[p]) {
                return false;
            }
        }

        for (p in x) {
            if (y.hasOwnProperty(p) ^ x.hasOwnProperty(p)) {
                return false;
            } else if (typeof y[p] !== typeof x[p]) {
                return false;
            }

            switch (typeof(x[p])) {
                case 'object':
                case 'function':

                    leftChain.push(x);
                    rightChain.push(y);

                    if (!compare2Objects(x[p], y[p])) {
                        return false;
                    }

                    leftChain.pop();
                    rightChain.pop();
                    break;

                default:
                    if (x[p] !== y[p]) {
                        return false;
                    }
                    break;
            }
        }

        return true;
    }

    for (i = 1; i < l; i++) {
        // clear before each compare as it can be cached
        leftChain = [];
        rightChain = [];

        if (!compare2Objects(arg0, args[i])) {
            return false;
        }
    }

    return true;
}

/**
 * 内容比较器
 * @type {deepCompare}
 */
Array.comparable = deepCompare
/**
 * 元素在集合中的索引
 * @param {*} a 元素
 * @param {function} [comparable] 比较器. 默认用 <code>===</code> 来比较.
 * @return {number}
 */
Array.prototype.at = function(a, comparable){
    comparable = comparable || Array.comparable
    if(!comparable) {
        return this.indexOf(a);
    }
    for(var i = 0; i < this.length; i++){
        if(comparable(a, this[i])){
            return i;
        }
    }
    return -1;
}
/**
 * 集合中是否包含元素
 * @param {*} a 元素
 * @return {boolean}
 */
Array.prototype.contains = function(a){
    return this.at(a) > -1;
}

/**
 * 元素唯一
 * @example
 * [1,3,2,3].unique() // returns [1,3,2]
 * @return {[]} 不重复元素的集合
 */
Array.prototype.unique = function(){
    return this.reduce(function (c, v) {
        if (!c.contains(v)){
            c.push(v);
        }
        return c;
    }, []);
}
/**
 * 全集
 * @example
 * [[1],[2],[3]].unionAll([[2],[4],[6]]) // returns [[1], [2], [3], [2], [4], [6]]
 * @return {*[]}
 */
Array.prototype.unionAll = function() {
    return Array.prototype.concat.apply(this, arguments);
}
/**
 * 并集
 * @example
 * [[1],[2],[3]].union([[2],[4],[6]]) // returns [[1], [2], [3], [4], [6]]
 * @return {*[]}
 */
Array.prototype.union = function () {
    return Array.prototype.unionAll.apply(this, arguments).unique();
}
/**
 * 差集
 * @example
 * [[1],[2],[3]].minus([[2],[4],[6]]) // returns [[1], [3]]
 * [[2],[4],[6]].minus([[1],[2],[3]]) // returns [[4], [6]]
 * @param {Array} a 要剔除的元素集合
 * @return {*[]}
 */
Array.prototype.minus = function (a) {
    return this.reduce(function (c, v) {
        if (!a.contains(v)){
            c.push(v);
        }
        return c;
    }, []);
}
/**
 * 补集
 * @example
 * [[1],[2],[3]].complement([[2],[4],[6]]) // returns [[1], [3], [4], [6]]
 * @param {Array} a
 * @return {*[]}
 */
Array.prototype.complement = function (a) {
    return this.minus(a).concat(a.minus(this));
}
/**
 * 交集
 * @example
 * [[1],[2],[3]].intersect([[2],[4],[6]]) // returns [[2]
 * @param {Array} a
 * @return {*[]}
 */
Array.prototype.intersect = function (a) {
    return this.reduce(function (c, v) {
        if (a.contains(v)){
            c.push(v);
        }
        return c;
    }, []);
}

/**
 * 数组求和
 * @example
 * // returns 11
 * [2,4,5].sum()
 * // returns 266
 * var arr = [
 *     {yu: 113, shu: 123, wai: 115, wu: 75, hua: 90, sheng: 100},
 *     {yu: 123, shu: 143, wai: 123, wu: 90, hua: 100, sheng: 100}
 * ]
 * arr.sum(it=>it.shu)
 * // returns 740
 * arr.sum(it=>it.yu + it.shu + it.wai)
 * // returns {yu: 236, shu: 266, wai:238}
 * arr.sum(['yu','shu','wai'])
 * @param {function|Array} [field] 求和条件
 * @returns {number|Object}
 */
Array.prototype.sum = function(field){
    var a = this;
    return typeof field==='function'?Array.prototype.reduce.call(a,function(c,v){
            return c += field(v);
        },0)
        :Array.isArray(field)?Array.prototype.reduce.call(field,function(c, v){
                c[v] = (c[v]||0) + Array.prototype.sum.call(a, function(it){
                    return it[v];
                });
                return c;
            },{})
            :!field?Array.prototype.reduce.call(a,function(c,v){
                return c += v;
            },0):0;
}
/**
 * 分组
 * @example
 * var arr = [
 * {code: "001", language: "2", content: "3"},
 * {code: "001", language: "2", content: "4"},
 * {code: "001", language: "3", content: "5"},
 * {code: "001", language: "3", content: "6"},
 * {code: "002", language: "1", content: "3"},
 * {code: "002", language: "2", content: "3"},
 * {code: "001", language: "2", content: "4"}
 * ]
 * arr.groupBy(it=>it.code)
 * arr.groupBy('code')
 * @param {string|function} group 分组字符段(一个)
 * @return {Object} 以分组字段为key的Object
 */
Array.prototype.groupBy = function(group){
    return group && typeof group==='function'?Array.prototype.reduce.call(this, function(c, v){
        var k = group(v);
        c[k] = (c[k]||[]).concat(v);
        return c;
    },{}):group && typeof group==='string' && group.length?this.reduce(function(c, v){
        var k = v[group];
        c[k] = (c[k]||[]).concat(v);
        return c;
    },{}):this;
}

感觉注释都写的差不多了.