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

例如,给定此对象数组:

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


当前回答

var newArr = data.reduce((acc, cur) => {
    const existType = acc.find(a => a.Phase === cur.Phase);
    if (existType) {
        existType.Value += +cur.Value;
        return acc;
    }

    acc.push({
        Phase: cur.Phase,
        Value: +cur.Value
    });
    return acc;
}, []);

其他回答

为了补充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;
}());

让我们生成一个通用的Array.protocol.groupBy()工具。为了多样化,让我们在递归方法上使用ES6 fancyty扩展运算符进行Haskell式模式匹配。同样,让我们让Array.prototype.groupBy()接受一个回调,该回调将项(e)、索引(i)和应用的数组(a)作为参数。

Array.prototype.groupBy=函数(cb){返回函数迭代([x,…xs],i=0,r=[[],[]]){cb(x,i,[x,…xs])?(r[0].推(x),r):(r[1].推(x),r);是否返回xs.length?迭代(xs,++i,r):r;}(本);};var arr=[0,1,2,3,4,5,6,7,8,9],res=arr.groupBy(e=>e<5);console.log(res);

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'));

基于以前的答案

const groupBy = (prop) => (xs) =>
  xs.reduce((rv, x) =>
    Object.assign(rv, {[x[prop]]: [...(rv[x[prop]] || []), x]}), {});

如果您的环境支持,使用对象扩展语法会更好一些。

const groupBy = (prop) => (xs) =>
  xs.reduce((acc, x) => ({
    ...acc,
    [ x[ prop ] ]: [...( acc[ x[ prop ] ] || []), x],
  }), {});

在这里,我们的reducer接受部分形成的返回值(从一个空对象开始),并返回一个由上一个返回值的展开成员组成的对象,以及一个新成员,该成员的键是从prop处的当前iteree值计算的,其值是该prop的所有值以及当前值的列表。

var arr = [ 
    { Phase: "Phase 1", `enter code here`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" }
];

创建并清空对象。循环遍历arr并添加使用Phase作为obj的唯一键。在循环遍历arr时,保持更新obj中的键总数。

const obj = {};
arr.forEach((item) => {
  obj[item.Phase] = obj[item.Phase] ? obj[item.Phase] + 
  parseInt(item.Value) : parseInt(item.Value);
});

结果如下:

{ "Phase 1": 50, "Phase 2": 130 }

循环通过obj形成表单和resultArr。

const resultArr = [];
for (item in obj) {
  resultArr.push({ Phase: item, Value: obj[item] });
}
console.log(resultArr);