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

例如,给定此对象数组:

[ 
    { 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,然后遍历生成的对象,自己计算总数?


当前回答

使用ES6 Map对象:

/***@描述*采用Array<V>和分组函数,*并返回由分组函数分组的数组的Map。**@param list V类型的数组。*@param keyGetter一个函数,将数组类型V作为输入,并返回类型K的值。*K通常是V的属性键。**@返回由分组函数分组的数组的映射。*///导出函数组By<K,V>(列表:Array<V>,keyGetter:(输入:V)=>K):Map<K,Array<V>>{//const-map=new map<K,Array<V>>();函数groupBy(list,keyGetter){const-map=new map();list.forEach((项)=>{const key=keyGetter(项);constcollection=map.get(key);if(!集合){map.set(键,[项]);}其他{collection.push(项);}});回归图;}//示例用法常量宠物=[{type:“Dog”,name:“Spot”},{类型:“猫”,名称:“老虎”},{类型:“狗”,名称:“路虎”},{类型:“猫”,名称:“利奥”}];const grouped=groupBy(宠物,宠物=>宠物类型);console.log(分组.get(“Dog”));//->〔{类型:“狗”,名称:“斑点”},{类型“狗”、名称:“漫游者”}〕console.log(分组.get(“Cat”));//->〔{类型:“猫”,名称:“老虎”},{类型“猫”、名称:“狮子座”}〕const奇数=符号();const even=符号();常量=[1,2,3,4,5,6,7];const oddEven=groupBy(数字,x=>(x%2===1?奇数:偶数));console.log(oddEven.get(奇数));//->[1,3,5,7]console.log(oddEven.get(偶数));//->[2,4,6]

关于地图:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map

其他回答

/***数组分组依据*@类别数组*@function arrayGroupBy*@return{object}{“fieldName”:〔{…}〕,…}*@静态*@作者hht*@param{string}}密钥组密钥*@param{array}数据数组**@示例01* --------------------------------------------------------------------------*从“@xx/utils”导入{arrayGroupBy};*常量数组=[* {*type:'资产',*name:'zhangsan',*年龄:33岁,* },* {*类型:'config',*name:“a”,*年龄:13岁,* },* {*类型:'run',*名称:'lisi',*年龄:“3”,* },* {*类型:'xx',*name:'timo',*年龄:'4',* },*];*arrayGroupBy(array,'type',);**结果:{*资产:[{年龄:'33',名称:'zhangsan',类型:'assets'}],*config:[{age:“13”,名称:“a”,类型:“config”}],*运行:[{age:“3”,名称:“lisi”,类型:“run”}],*xx:[{age:“4”,名称:“timo”,类型:“xx”}],* };**@example示例02 null* --------------------------------------------------------------------------*常量数组=空;*arrayGroupBy(数组,“类型”);**结果:{}**@example示例03键取消绑定* --------------------------------------------------------------------------*常量数组=[* {*type:'资产',*name:'zhangsan',*年龄:33岁,* },* {*类型:'config',*name:“a”,*年龄:13岁,* },* {*类型:'run',*名称:'lisi',*年龄:“3”,* },* {*类型:'xx',*name:'timo',*年龄:'4',* },*];*arrayGroupBy(数组,“xx”);** {}**/const arrayGroupBy=(data,key)=>{if(!data||!Array.isArray(data))返回{};常量groupObj={};data.forEach((项)=>{if(!item[key])返回;const fieldName=项[key];if(!groupObj[fieldName]){groupObj[fieldName]=[item];回来}groupObj[fieldName].push(项);});返回groupObj;};常量数组=[{type:'资产',name:'zhangsan',年龄:33岁,},{类型:'config',name:“a”,年龄:13岁,},{类型:'run',名称:'lisi',年龄:“3”,},{类型:'run',名称:“wangmazi”,年龄:“3”,},{类型:'xx',name:'timo',年龄:'4',},];console.dir(arrayGroupBy(array,'type'))<p>description('arrayGroupBy match',()=>{常量数组=[{type:'资产',name:'zhangsan',年龄:33岁,},{类型:'config',name:“a”,年龄:13岁,},{类型:'run',名称:'lisi',年龄:“3”,},{类型:'xx',name:'timo',年龄:'4',},];测试('arrayGroupBy…',()=>{常量结果={资产:[{年龄:'33',名称:'zhangsan',类型:'assets'}],config:[{age:“13”,名称:“a”,类型:“config”}],运行:[{age:“3”,名称:“lisi”,类型:“run”}],xx:[{age:“4”,名称:“timo”,类型:“xx”}],};expect(arrayGroupBy(array,'type')).toEqual(result);});test('arrayGroupBy不匹配..',()=>{//结果expect(arrayGroupBy(array,'xx')).toEqual({});});test('arrayGroupBy null',()=>{let数组=空;expect(arrayGroupBy(array,'type')).toEqual({});});test('arrayGroupBy undefined',()=>{let array=未定义;expect(arrayGroupBy(array,'type')).toEqual({});});test('arrayGroupBy空',()=>{let数组=[];expect(arrayGroupBy(array,'type')).toEqual({});});});</p>

使用linq.js可能更容易做到这一点,它是linq在JavaScript(DEMO)中的真正实现:

var linq = Enumerable.From(data);
var result =
    linq.GroupBy(function(x){ return x.Phase; })
        .Select(function(x){
          return {
            Phase: x.Key(),
            Value: x.Sum(function(y){ return y.Value|0; })
          };
        }).ToArray();

结果:

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

或者,更简单地使用基于字符串的选择器(DEMO):

linq.GroupBy("$.Phase", "",
    "k,e => { Phase:k, Value:e.Sum('$.Value|0') }").ToArray();

虽然linq的回答很有趣,但它也很重。我的方法有些不同:

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;
}());

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

您可以在JSBin上看到它的作用。

我在Undercore中没有看到任何东西可以做已经做的事情,尽管我可能会错过它。它与_.incontains非常相似,但使用_.isEqual而不是==进行比较。除此之外,其余部分都是针对具体问题的,尽管只是试图通用。

现在DataGrouper.sum(data,[“Phase”])返回

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

DataGrouper.sum(data,[“Phase”,“Step”])返回

[
    {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}
]

但和在这里只是一个势函数。您可以根据需要注册其他人:

DataGrouper.register("max", function(item) {
    return _.extend({}, item.key, {Max: _.reduce(item.vals, function(memo, node) {
        return Math.max(memo, Number(node.Value));
    }, Number.NEGATIVE_INFINITY)});
});

现在DataGrouper.max(data,[“Phase”,“Step”])将返回

[
    {Phase: "Phase 1", Step: "Step 1", Max: 10},
    {Phase: "Phase 1", Step: "Step 2", Max: 20},
    {Phase: "Phase 2", Step: "Step 1", Max: 30},
    {Phase: "Phase 2", Step: "Step 2", Max: 40}
]

或者如果您注册了:

DataGrouper.register("tasks", function(item) {
    return _.extend({}, item.key, {Tasks: _.map(item.vals, function(item) {
      return item.Task + " (" + item.Value + ")";
    }).join(", ")});
});

然后调用DataGrouper.tasks(data,[“Phase”,“Step”])将得到

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

DataGrouper本身是一个函数。可以用数据和要分组的财产列表来调用它。它返回一个数组,该数组的元素是具有两个财产的对象:key是分组财产的集合,vals是包含不在key中的其余财产的对象数组。例如,DataGrouper(data,[“Phase”,“Step”])将产生:

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

DataGrouper.register接受一个函数并创建一个新函数,该函数接受初始数据和分组依据的财产。然后,这个新函数采用上述输出格式,并依次对每个输出格式运行函数,返回一个新数组。生成的函数根据您提供的名称存储为DataGrouper的属性,如果您只需要本地引用,也会返回。

这是很多解释。我希望代码相当简单!

let groupbyKeys = function(arr, ...keys) {
  let keysFieldName = keys.join();
  return arr.map(ele => {
    let keysField = {};
    keysField[keysFieldName] = keys.reduce((keyValue, key) => {
      return keyValue + ele[key]
    }, "");
    return Object.assign({}, ele, keysField);
  }).reduce((groups, ele) => {
    (groups[ele[keysFieldName]] = groups[ele[keysFieldName]] || [])
      .push([ele].map(e => {
        if (keys.length > 1) {
          delete e[keysFieldName];
        }
        return e;
    })[0]);
    return groups;
  }, {});
};

console.log(groupbyKeys(array, 'Phase'));
console.log(groupbyKeys(array, 'Phase', 'Step'));
console.log(groupbyKeys(array, 'Phase', 'Step', 'Task'));

您可以使用Alasql JavaScript库来实现:

var data = [ { Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
             { Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" }];

var res = alasql('SELECT Phase, Step, SUM(CAST([Value] AS INT)) AS [Value] \
                  FROM ? GROUP BY Phase, Step',[data]);

在jsFiddle尝试这个示例。

BTW:在大型阵列(100000条记录及以上)上,Alasql比Linq更快。参见jsPref中的测试。

评论:

这里我将Value放在方括号中,因为Value是SQL中的关键字我必须使用CAST()函数将字符串值转换为数字类型。