按数组中的对象分组最有效的方法是什么?

例如,给定此对象数组:

[ 
    { Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
    { Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },
    { Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
    { Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
    { Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
    { Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
    { Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
    { Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
]

我正在表格中显示这些信息。我想通过不同的方法进行分组,但我想对值求和。

我将Undercore.js用于其groupby函数,这很有用,但并不能完成全部任务,因为我不希望它们“拆分”,而是“合并”,更像SQL groupby方法。

我要找的是能够合计特定值(如果需要)。

因此,如果我按阶段分组,我希望收到:

[
    { Phase: "Phase 1", Value: 50 },
    { Phase: "Phase 2", Value: 130 }
]

如果我组了阶段/步骤,我会收到:

[
    { Phase: "Phase 1", Step: "Step 1", Value: 15 },
    { Phase: "Phase 1", Step: "Step 2", Value: 35 },
    { Phase: "Phase 2", Step: "Step 1", Value: 55 },
    { Phase: "Phase 2", Step: "Step 2", Value: 75 }
]

是否有一个有用的脚本,或者我应该坚持使用Undercore.js,然后遍历生成的对象,自己计算总数?


当前回答

如果您需要通过以下方式进行多组:


    const populate = (entireObj, keys, item) => {
    let keysClone = [...keys],
        currentKey = keysClone.shift();

    if (keysClone.length > 0) {
        entireObj[item[currentKey]] = entireObj[item[currentKey]] || {}
        populate(entireObj[item[currentKey]], keysClone, item);
    } else {
        (entireObj[item[currentKey]] = entireObj[item[currentKey]] || []).push(item);
    }
}

export const groupBy = (list, key) => {
    return list.reduce(function (rv, x) {

        if (typeof key === 'string') (rv[x[key]] = rv[x[key]] || []).push(x);

        if (typeof key === 'object' && key.length) populate(rv, key, x);

        return rv;

    }, {});
}

const myPets = [
    {name: 'yaya', type: 'cat', color: 'gray'},
    {name: 'bingbang', type: 'cat', color: 'sliver'},
    {name: 'junior-bingbang', type: 'cat', color: 'sliver'},
    {name: 'jindou', type: 'cat', color: 'golden'},
    {name: 'dahuzi', type: 'dog', color: 'brown'},
];

// run 
groupBy(myPets, ['type', 'color']));

// you will get object like: 

const afterGroupBy = {
    "cat": {
        "gray": [
            {
                "name": "yaya",
                "type": "cat",
                "color": "gray"
            }
        ],
        "sliver": [
            {
                "name": "bingbang",
                "type": "cat",
                "color": "sliver"
            },
            {
                "name": "junior-bingbang",
                "type": "cat",
                "color": "sliver"
            }
        ],
        "golden": [
            {
                "name": "jindou",
                "type": "cat",
                "color": "golden"
            }
        ]
    },
    "dog": {
        "brown": [
            {
                "name": "dahuzi",
                "type": "dog",
                "color": "brown"
            }
        ]
    }
};

其他回答

为了补充Scott Sauyet的答案,一些人在评论中询问如何使用他的函数按值1、值2等分组,而不是仅对一个值分组。

只需编辑他的求和函数:

DataGrouper.register("sum", function(item) {
    return _.extend({}, item.key,
        {VALUE1: _.reduce(item.vals, function(memo, node) {
        return memo + Number(node.VALUE1);}, 0)},
        {VALUE2: _.reduce(item.vals, function(memo, node) {
        return memo + Number(node.VALUE2);}, 0)}
    );
});

保持主组(DataGrouper)不变:

var DataGrouper = (function() {
    var has = function(obj, target) {
        return _.any(obj, function(value) {
            return _.isEqual(value, target);
        });
    };

    var keys = function(data, names) {
        return _.reduce(data, function(memo, item) {
            var key = _.pick(item, names);
            if (!has(memo, key)) {
                memo.push(key);
            }
            return memo;
        }, []);
    };

    var group = function(data, names) {
        var stems = keys(data, names);
        return _.map(stems, function(stem) {
            return {
                key: stem,
                vals:_.map(_.where(data, stem), function(item) {
                    return _.omit(item, names);
                })
            };
        });
    };

    group.register = function(name, converter) {
        return group[name] = function(data, names) {
            return _.map(group(data, names), converter);
        };
    };

    return group;
}());

我想建议一下我的方法。首先,分开分组和聚合。让我们声明原型“groupby”函数。它需要另一个函数为要分组的每个数组元素生成“哈希”字符串。

Array.prototype.groupBy = function(hash){
  var _hash = hash ? hash : function(o){return o;};

  var _map = {};
  var put = function(map, key, value){
    if (!map[_hash(key)]) {
        map[_hash(key)] = {};
        map[_hash(key)].group = [];
        map[_hash(key)].key = key;

    }
    map[_hash(key)].group.push(value); 
  }

  this.map(function(obj){
    put(_map, obj, obj);
  });

  return Object.keys(_map).map(function(key){
    return {key: _map[key].key, group: _map[key].group};
  });
}

分组完成后,您可以根据需要聚合数据

data.groupBy(function(o){return JSON.stringify({a: o.Phase, b: o.Step});})
    /* aggreagating */
    .map(function(el){ 
         var sum = el.group.reduce(
           function(l,c){
             return l + parseInt(c.Value);
           },
           0
         );
         el.key.Value = sum; 
         return el.key;
    });

一般来说,它是有效的。我已经在chrome控制台中测试了这段代码。并随时改进和发现错误;)

此解决方案采用任意函数(而不是键),因此比上述解决方案更灵活,并允许箭头函数,这与LINQ中使用的lambda表达式类似:

Array.prototype.groupBy = function (funcProp) {
    return this.reduce(function (acc, val) {
        (acc[funcProp(val)] = acc[funcProp(val)] || []).push(val);
        return acc;
    }, {});
};

注意:是否要扩展Array的原型取决于您。

大多数浏览器支持的示例:

[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(function(c){return c.a;})

使用箭头函数(ES6)的示例:

[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(c=>c.a)

以上两个示例都返回:

{
  "1": [{"a": 1, "b": "b"}, {"a": 1, "c": "c"}],
  "2": [{"a": 2, "d": "d"}]
}

使用lodash库很简单

let temp = []
  _.map(yourCollectionData, (row) => {
    let index = _.findIndex(temp, { 'Phase': row.Phase })
    if (index > -1) {
      temp[index].Value += row.Value 
    } else {
      temp.push(row)
    }
  })

Array.prototype.groupBy=函数(groupingKeyFn){if(groupingKeyFn的类型!=='函数'){throw new Error(“groupBy将函数作为唯一参数”);}返回this。reduce((result,item)=>{let key=groupingKeyFn(项);if(!result[key])result[key]=[];result[key].push(项);返回结果;}, {});}变量a=[{type:“video”,名称:“a”},{type:“image”,名称:“b”},{type:“video”,名称:“c”},{type:“blog”,名称:“d”},{type:“video”,名称:“e”},]console.log(a.groupBy((item)=>item.type));<script src=“https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js“></script>