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

例如,给定此对象数组:

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


使用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的属性,如果您只需要本地引用,也会返回。

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


我想建议一下我的方法。首先,分开分组和聚合。让我们声明原型“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控制台中测试了这段代码。并随时改进和发现错误;)


我从underscore.js fiddler那里借用了这个方法

window.helpers=(function (){
    var lookupIterator = function(value) {
        if (value == null){
            return function(value) {
                return value;
            };
        }
        if (typeof value === 'function'){
                return value;
        }
        return function(obj) {
            return obj[value];
        };
    },
    each = function(obj, iterator, context) {
        var breaker = {};
        if (obj == null) return obj;
        if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) {
            obj.forEach(iterator, context);
        } else if (obj.length === +obj.length) {
            for (var i = 0, length = obj.length; i < length; i++) {
                if (iterator.call(context, obj[i], i, obj) === breaker) return;
            }
        } else {
            var keys = []
            for (var key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) keys.push(key)
            for (var i = 0, length = keys.length; i < length; i++) {
                if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
            }
        }
        return obj;
    },
    // An internal function used for aggregate "group by" operations.
    group = function(behavior) {
        return function(obj, iterator, context) {
            var result = {};
            iterator = lookupIterator(iterator);
            each(obj, function(value, index) {
                var key = iterator.call(context, value, index, obj);
                behavior(result, key, value);
            });
            return result;
        };
    };

    return {
      groupBy : group(function(result, key, value) {
        Object.prototype.hasOwnProperty.call(result, key) ? result[key].push(value) :              result[key] = [value];
        })
    };
})();

var arr=[{a:1,b:2},{a:1,b:3},{a:1,b:1},{a:1,b:2},{a:1,b:3}];
 console.dir(helpers.groupBy(arr,"b"));
 console.dir(helpers.groupBy(arr,function (el){
   return el.b>2;
 }));

_.groupBy([{tipo: 'A' },{tipo: 'A'}, {tipo: 'B'}], 'tipo');
>> Object {A: Array[2], B: Array[1]}

发件人:http://underscorejs.org/#groupBy


您可以使用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()函数将字符串值转换为数字类型。


Array.prototype.groupBy = function(keyFunction) {
    var groups = {};
    this.forEach(function(el) {
        var key = keyFunction(el);
        if (key in groups == false) {
            groups[key] = [];
        }
        groups[key].push(el);
    });
    return Object.keys(groups).map(function(key) {
        return {
            key: key,
            values: groups[key]
        };
    });
};

我会检查一下lodash组,它似乎正是你想要的。它也很轻,非常简单。

Fiddle示例:https://jsfiddle.net/r7szvt5k/

如果数组名称为arr,则带有lodash的groupBy仅为:

import groupBy from 'lodash/groupBy';
// if you still use require:
// const groupBy = require('lodash/groupBy');

const a = groupBy(arr, function(n) {
  return n.Phase;
});
// a is your array grouped by Phase attribute

如果希望避免使用外部库,可以简洁地实现groupBy()的普通版本,如下所示:

var groupBy=函数(xs,key){返回xs.reduce(函数(rv,x){(rv[x[key]]=rv[x[键]]| |[]).push(x);返回rv;}, {});};console.log(groupBy(['one','two','three'],'length'));//=>{“3”:[“1”,“2”],“5”:[”3“]}


一种新的方法,有一个用于分组的对象和另外两个用于创建键的函数,并获得一个包含所需分组项的对象和另一个用于添加值的键。

常量groupBy=(数组,组,valueKey)=>{常量getKey=o=>groups.map(k=>o[k]).join('|'),getObject=o=>Object.fromEntries([…groups.map(k=>[k,o[k]]),[valueKey,0]);groups=[].contat(组);return Object.values(array.reduce((r,o)=>{(r[getKey(o)]??=getObject(o))[valueKey]+=+o[valueKey];返回r;}, {}));},data=[{阶段:“阶段1”,步骤:“步骤1”,任务:“任务1”,值:“5”},{阶段“阶段1“,步骤:”步骤1“,任务:”任务2“,值:”10“},{阶段:”阶段1“、步骤:”第2“、任务:”第1“、值:”15“}、{阶段”阶段1”、步骤:“第2”、任务:“2”、值:“20”}、{阶段“第2阶段”,步骤“步骤:”第一步“,任务“任务:”,值“25”}阶段:“阶段2”,步骤:“步骤1”,任务:“任务2”,值:“30”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务1”,值:”35“},{阶段:”阶段2“,步骤:”步骤2“,任务:”任务2“,值::”40“}];console.log(groupBy(data,'Phase','Value'));console.log(groupBy(数据,['Phase','Step'],'Value'));.作为控制台包装{最大高度:100%!重要;顶部:0;}

旧方法:

虽然这个问题有一些答案,而且答案看起来有点过于复杂,但我建议使用香草Javascript进行分组,并使用嵌套的(如果需要)Map。

函数groupBy(数组,组,valueKey){var map=新地图;groups=[].contat(组);返回数组.reduce((r,o)=>{groups.reduce((m,k,i,{length})=>{var子;如果(m.has(o[k]))返回m.get(o[k]);如果(i+1==长度){child=对象.assign(…groups.map(k=>({[k]:o[k]})),{[valueKey]:0});r.push(儿童);}其他{child=新地图;}m.set(o[k],子);回归儿童;},map)[valueKey]+=+o[valueKey];返回r;}, [])};var data=[{阶段:“阶段1”,步骤:“步骤1”,任务:“任务1”,值:“5”},{阶段“阶段1“,步骤:”步骤1“,任务:”任务2“,值:”10“},{阶段:”阶段1“、步骤:”阶段2“,任务1“,值“15”}、{阶段”阶段1”、步骤:“阶段2”,任务2“、值:”20“}、{阶段2“、步骤1”、任务1“、任务1”、值:“25”},{阶段:“阶段2”,步骤:“步骤1”,任务:“任务2”,值:“30”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务1”,值:”35“},{阶段:”阶段2“,步骤:”步骤2“,任务:”任务2“,值::”40“}];console.log(groupBy(data,'Phase','Value'));console.log(groupBy(数据,['Phase','Step'],'Value'));.作为控制台包装{最大高度:100%!重要;顶部:0;}


使用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


groupByArray(xs, key) {
    return xs.reduce(function (rv, x) {
        let v = key instanceof Function ? key(x) : x[key];
        let el = rv.find((r) => r && r.key === v);
        if (el) {
            el.values.push(x);
        }
        else {
            rv.push({
                key: v,
                values: [x]
            });
        }
        return rv;
    }, []);
}

这一个输出数组。


无突变:

const groupBy = (xs, key) => xs.reduce((acc, x) => Object.assign({}, acc, {
  [x[key]]: (acc[x[key]] || []).concat(x)
}), {})

console.log(groupBy(['one', 'two', 'three'], 'length'));
// => {3: ["one", "two"], 5: ["three"]}

此解决方案采用任意函数(而不是键),因此比上述解决方案更灵活,并允许箭头函数,这与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"}]
}

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

让我们生成一个通用的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);


Ceasar的答案很好,但只适用于数组中元素的内部财产(字符串的长度)。

这个实现的工作方式更像:这个链接

const groupBy = function (arr, f) {
    return arr.reduce((out, val) => {
        let by = typeof f === 'function' ? '' + f(val) : val[f];
        (out[by] = out[by] || []).push(val);
        return out;
    }, {});
};

希望这有帮助。。。


使用ES6:

const groupBy = (items, key) => items.reduce(
  (result, item) => ({
    ...result,
    [item[key]]: [
      ...(result[item[key]] || []),
      item,
    ],
  }), 
  {},
);

这里有一个ES6版本,不会在空成员上中断

function groupBy (arr, key) {
  return (arr || []).reduce((acc, x = {}) => ({
    ...acc,
    [x[key]]: [...acc[x[key]] || [], x]
  }), {})
}

您可以从array.reduce()构建ES6映射。

const groupedMap = initialArray.reduce(
    (entryMap, e) => entryMap.set(e.id, [...entryMap.get(e.id)||[], e]),
    new Map()
);

这与其他解决方案相比有一些优势:

它不需要任何库(与例如_.groupBy()不同)您得到的是JavaScript Map而不是对象(例如,由_.groupBy()返回)。这有很多好处,包括:它会记住第一次添加项目的顺序,键可以是任何类型,而不仅仅是字符串。Map是比数组数组更有用的结果。但是,如果确实需要数组数组,则可以调用array.from(groupedMap.entries())(对于[key,group array]对的数组)或array.from(groupedMap.values()),(对于简单的数组数组)。它非常灵活;通常情况下,您计划使用此地图进行的任何操作都可以作为缩减的一部分直接完成。

作为最后一点的示例,假设我有一个对象数组,我想按id对其进行(浅)合并,如下所示:

const objsToMerge = [{id: 1, name: "Steve"}, {id: 2, name: "Alice"}, {id: 1, age: 20}];
// The following variable should be created automatically
const mergedArray = [{id: 1, name: "Steve", age: 20}, {id: 2, name: "Alice"}]

要做到这一点,我通常首先按id分组,然后合并每个结果数组。相反,您可以直接在reduce()中执行合并:

const mergedArray = Array.from(
    objsToMerge.reduce(
        (entryMap, e) => entryMap.set(e.id, {...entryMap.get(e.id)||{}, ...e}),
        new Map()
    ).values()
);

稍后编辑:

对于大多数目的来说,上述方法可能足够有效。但最初的问题是“最有效”的,正如一些人所指出的,上述解决方案并非如此。问题主要是为每个条目实例化一个新数组。我本以为JS解释器会优化这一点,但似乎并非如此。

有人建议进行编辑来解决这个问题,但看起来确实更复杂。原始代码段已经有点提高了可读性。如果你真的想这样做,请使用for循环!这不是罪!它需要一到两行代码,但它比函数技术更简单,尽管它并不短:

groupedMap = new Map();
for (const e of initialArray) {
    if (groupedMap.has(e.id)) {
        groupedMap.get(e.id).push(e)
    }
    else {
        groupedMap.set(e.id, [e])
    }
}

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

基于以前的答案

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

具有排序功能

export const groupBy = function groupByArray(xs, key, sortKey) {
      return xs.reduce(function(rv, x) {
        let v = key instanceof Function ? key(x) : x[key];
        let el = rv.find(r => r && r.key === v);

        if (el) {
          el.values.push(x);
          el.values.sort(function(a, b) {
            return a[sortKey].toLowerCase().localeCompare(b[sortKey].toLowerCase());
          });
        } else {
          rv.push({ key: v, values: [x] });
        }

        return rv;
      }, []);
    };

示例:

var state = [
    {
      name: "Arkansas",
      population: "2.978M",
      flag:
  "https://upload.wikimedia.org/wikipedia/commons/9/9d/Flag_of_Arkansas.svg",
      category: "city"
    },{
      name: "Crkansas",
      population: "2.978M",
      flag:
        "https://upload.wikimedia.org/wikipedia/commons/9/9d/Flag_of_Arkansas.svg",
      category: "city"
    },
    {
      name: "Balifornia",
      population: "39.14M",
      flag:
        "https://upload.wikimedia.org/wikipedia/commons/0/01/Flag_of_California.svg",
      category: "city"
    },
    {
      name: "Florida",
      population: "20.27M",
      flag:
        "https://upload.wikimedia.org/wikipedia/commons/f/f7/Flag_of_Florida.svg",
      category: "airport"
    },
    {
      name: "Texas",
      population: "27.47M",
      flag:
        "https://upload.wikimedia.org/wikipedia/commons/f/f7/Flag_of_Texas.svg",
      category: "landmark"
    }
  ];
console.log(JSON.stringify(groupBy(state,'category','name')));

从@mortb、@jmarceli的回答和这篇文章中,

我利用JSON.stringify()作为分组依据的PRIMITIVE VALUE多列的标识。

无第三方

function groupBy(list, keyGetter) {
    const map = new Map();
    list.forEach((item) => {
        const key = keyGetter(item);
        if (!map.has(key)) {
            map.set(key, [item]);
        } else {
            map.get(key).push(item);
        }
    });
    return map;
}

const pets = [
    {type:"Dog", age: 3, name:"Spot"},
    {type:"Cat", age: 3, name:"Tiger"},
    {type:"Dog", age: 4, name:"Rover"}, 
    {type:"Cat", age: 3, name:"Leo"}
];

const grouped = groupBy(pets,
pet => JSON.stringify({ type: pet.type, age: pet.age }));

console.log(grouped);

使用Lodash第三方

const pets = [
    {type:"Dog", age: 3, name:"Spot"},
    {type:"Cat", age: 3, name:"Tiger"},
    {type:"Dog", age: 4, name:"Rover"}, 
    {type:"Cat", age: 3, name:"Leo"}
];

let rslt = _.groupBy(pets, pet => JSON.stringify(
 { type: pet.type, age: pet.age }));

console.log(rslt);

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>


我对公认的答案进行了扩展,包括按多个财产分组,然后再加上,使其完全起作用,没有变异。观看演示https://stackblitz.com/edit/typescript-ezydzv

export interface Group {
  key: any;
  items: any[];
}

export interface GroupBy {
  keys: string[];
  thenby?: GroupBy;
}

export const groupBy = (array: any[], grouping: GroupBy): Group[] => {
  const keys = grouping.keys;
  const groups = array.reduce((groups, item) => {
    const group = groups.find(g => keys.every(key => item[key] === g.key[key]));
    const data = Object.getOwnPropertyNames(item)
      .filter(prop => !keys.find(key => key === prop))
      .reduce((o, key) => ({ ...o, [key]: item[key] }), {});
    return group
      ? groups.map(g => (g === group ? { ...g, items: [...g.items, data] } : g))
      : [
          ...groups,
          {
            key: keys.reduce((o, key) => ({ ...o, [key]: item[key] }), {}),
            items: [data]
          }
        ];
  }, []);
  return grouping.thenby ? groups.map(g => ({ ...g, items: groupBy(g.items, grouping.thenby) })) : groups;
};

data = [{id:1, name:'BMW'}, {id:2, name:'AN'}, {id:3, name:'BMW'}, {id:1, name:'NNN'}]
key = 'id'//try by id or name
data.reduce((previous, current)=>{
    previous[current[key]] && previous[current[key]].length != 0 ? previous[current[key]].push(current) : previous[current[key]] = new Array(current)
    return previous;
}, {})

基于@Ceasar Bautista的原始想法,我修改了代码并使用typescript创建了一个groupBy函数。

static groupBy(data: any[], comparator: (v1: any, v2: any) => boolean, onDublicate: (uniqueRow: any, dublicateRow: any) => void) {
    return data.reduce(function (reducedRows, currentlyReducedRow) {
      let processedRow = reducedRows.find(searchedRow => comparator(searchedRow, currentlyReducedRow));

      if (processedRow) {
        // currentlyReducedRow is a dublicateRow when processedRow is not null.
        onDublicate(processedRow, currentlyReducedRow)
      } else {
        // currentlyReducedRow is unique and must be pushed in the reducedRows collection.
        reducedRows.push(currentlyReducedRow);
      }

      return reducedRows;
    }, []);
  };

此函数接受一个回调(比较器)和一个第二个回调(onDuplicate),该回调比较行并查找副本。

用法示例:

data = [
    { name: 'a', value: 10 },
    { name: 'a', value: 11 },
    { name: 'a', value: 12 },
    { name: 'b', value: 20 },
    { name: 'b', value: 1 }
  ]

  private static demoComparator = (v1: any, v2: any) => {
    return v1['name'] === v2['name'];
  }

  private static demoOnDublicate = (uniqueRow, dublicateRow) => {
    uniqueRow['value'] += dublicateRow['value'];    
  };

使命感

groupBy(data, demoComparator, demoOnDublicate) 

将执行计算值和的分组。

{name: "a", value: 33}
{name: "b", value: 21}

我们可以根据项目的需要创建任意多个回调函数,并根据需要聚合这些值。在一个例子中,我需要合并两个数组,而不是求和数据。


MDN的Array.reduce()文档中有此示例。

// Grouping objects by a property
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Grouping_objects_by_a_property#Grouping_objects_by_a_property

var people = [
  { name: 'Alice', age: 21 },
  { name: 'Max', age: 20 },
  { name: 'Jane', age: 20 }
];

function groupBy(objectArray, property) {
  return objectArray.reduce(function (acc, obj) {
    var key = obj[property];
    if (!acc[key]) {
      acc[key] = [];
    }
    acc[key].push(obj);
    return acc;
  }, {});
}

var groupedPeople = groupBy(people, 'age');
// groupedPeople is:
// { 
//   20: [
//     { name: 'Max', age: 20 }, 
//     { name: 'Jane', age: 20 }
//   ], 
//   21: [{ name: 'Alice', age: 21 }] 
// }

正确答案——只是浅分组。理解减少是很好的。问题还提供了额外总计计算的问题。

这里是一个由一些字段组成的对象数组的REAL GROUP BY,该字段具有1)计算的键名称和2)通过提供所需键的列表来实现分组级联的完整解决方案并将其唯一值转换为根键,如SQL GROUP BY做

常量inputArray=[{阶段:“阶段1”,步骤:“步骤1”,任务:“任务1”,值:“5”},{阶段:“阶段1”,步骤:“步骤1”,任务:“任务2”,值:“10”},{阶段:“阶段1”,步骤:“步骤2”,任务:“任务1”,值:“15”},{阶段:“阶段1”,步骤:“步骤2”,任务:“任务2”,值:“20”},{阶段:“阶段2”,步骤:“步骤1”,任务:“任务1”,值:“25”},{阶段:“阶段2”,步骤:“步骤1”,任务:“任务2”,值:“30”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务1”,值:“35”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务2”,值:“40”}];var outObject=inputArray.reduce(函数(a,e){//GROUP BY估计密钥(estKey)可能只是一个普通密钥//a——累加器结果对象//e——依次检查的元素,即在此位置测试的元素//可以计算新的分组名称,但必须基于实字段的实际值设estKey=(e['Phase']);(a[estKey]?a[estKey]:(a[est Key]=null | |[])).push(e);返回a;}, {});console.log(outObject);

使用estKey--您可以按多个字段分组,添加其他聚合、计算或其他处理。

您还可以递归地分组数据。例如,最初按阶段分组,然后按步骤字段分组等等脂肪休息数据。

常量输入数组=[{阶段:“阶段1”,步骤:“步骤1”,任务:“任务1”,值:“5”},{阶段:“阶段1”,步骤:“步骤1”,任务:“任务2”,值:“10”},{阶段:“阶段1”,步骤:“步骤2”,任务:“任务1”,值:“15”},{阶段:“阶段1”,步骤:“步骤2”,任务:“任务2”,值:“20”},{阶段:“阶段2”,步骤:“步骤1”,任务:“任务1”,值:“25”},{阶段:“阶段2”,步骤:“步骤1”,任务:“任务2”,值:“30”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务1”,值:“35”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务2”,值:“40”}];/***获取obj WITH属性的SHALLOW副本的小助手*/const rmProp=(obj,prop)=>(({[prop]:_,…rest})=>rest)(obj))/***按关键字分组数组。结果数组的根键是value*指定密钥的。**@param{Array}src源数组*@param{String}key分组依据的by key*@return{Object}将分组对象作为值的对象*/const grpBy=(src,key)=>src.reduce((a,e)=>((a[e[key]]=a[e[键]]| |[]).push(rmProp(e,键)),a), {});/***如果对象数组仅由具有单个值的对象组成,则折叠该数组。*将其替换为剩余值。*/const blowObj=obj=>Array.isArray(obj)&&obj.length==1&&Object.values(obj[0]).length==1?对象.值(obj[0])[0]:obj;/***带有键列表的递归分组`keyList`可以是数组*或UNIQUE值将使用逗号分隔的键名列表*成为结果对象的关键点。*/const grpByReal=函数(src,keyList){const[key,…rest]=Array.isArray(keyList)?keyList:String(keyList).trim().split(/\s*,\s*/);常量res=键?grpBy(src,key):[…src];if(剩余长度){for(常量k,单位:res){res[k]=grpByReal(res[k],其余)}}其他{for(常量k,单位:res){res[k]=blowObj(res[k])}}回报率;}console.log(JSON.stringify(grpByReal(inputArray,'阶段,步骤,任务'),null,2));


您可以在数组上使用forEach并构造一组新的项。以下是如何使用FlowType注释实现这一点

// @flow

export class Group<T> {
  tag: number
  items: Array<T>

  constructor() {
    this.items = []
  }
}

const groupBy = (items: Array<T>, map: (T) => number) => {
  const groups = []

  let currentGroup = null

  items.forEach((item) => {
    const tag = map(item)

    if (currentGroup && currentGroup.tag === tag) {
      currentGroup.items.push(item)
    } else {
      const group = new Group<T>()
      group.tag = tag
      group.items.push(item)
      groups.push(group)

      currentGroup = group
    }
  })

  return groups
}

export default groupBy

玩笑测试可以是这样的

// @flow

import groupBy from './groupBy'

test('groupBy', () => {
  const items = [
    { name: 'January', month: 0 },
    { name: 'February', month: 1 },
    { name: 'February 2', month: 1 }
  ]

  const groups = groupBy(items, (item) => {
    return item.month
  })

  expect(groups.length).toBe(2)
  expect(groups[1].items[1].name).toBe('February 2')
})

ES6基于reduce的版本,支持iteratee函数。

如果未提供iteratee函数,则工作正常:

const data=〔{id:1,得分:2},{id:1,得分:3},{id:2,得分:2},{id:2,得分:4}〕常量组=(arr,k)=>arr.reduce((r,c)=>(r[c[k]]=[…r[c[k]]||[],c],r),{});常量组By=(arr,k,fn=()=>真)=>arr.reduce((r,c)=>(fn(c[k])?r[c[k]]=[…r[c[k]]| |[],c]:null,r),{});console.log(group(data,'id'))//通过`reduce分组`console.log(groupBy(data,'id'))//如果省略了“fn”,则结果相同console.log(groupBy(data,'score',x=>x>2))//使用iteratee分组

关于OP问题:

const data=〔{阶段:“阶段1”,步骤:“步骤1”,任务:“任务1”,值:“5”},{阶段“阶段1“,步骤:”步骤1“,任务:”任务2“,值:”10“},{阶段:”阶段1“、步骤:”阶段2“,任务1“,值“15”}、{阶段”阶段1”、步骤:“阶段2”、任务:”“任务2”、值:”20“}、{阶段“2”,步骤“步骤:”“步骤1“、任务:“1”、值“25”},{阶段:“阶段2”,步骤:“步骤1”,任务:“任务2”,值:“30”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务1”,值:”35“},{阶段:”阶段2“,步骤:”步骤2“,任务:”任务2“,值::”40“}]常量组By=(arr,k)=>arr.reduce((r,c)=>(r[c[k]]=[…r[c[k]]||[],c],r),{});常量组With=(arr,k,fn=()=>真)=>arr.reduce((r,c)=>(fn(c[k])?r[c[k]]=[…r[c[k]]| |[],c]:null,r),{});console.log(groupBy(数据,'Phase'))console.log(groupWith(data,'Value',x=>x>30))//按`Value`>30分组

另一个ES6版本,它反转分组,将值用作键,将键用作分组值:

常量数据=[{A:“1”},{B:“10”}、{C:“10”}]常量组键=arr=>arr.reduce((r,c)=>(Object.keys(c).map(x=>r[c[x]]=[…r[c[x]]||[],x]),r),{});console.log(groupKeys(数据))

注意:函数以简短的形式(一行)发布,目的是为了简洁,并仅表达想法。您可以展开它们并添加其他错误检查等。


下面的函数允许对任意字段进行groupBy(和求和值-OP需要的)。在解决方案中,我们定义cmp函数来根据分组字段比较两个对象。在设w=。。。我们创建子集对象x字段的副本。在y[sumBy]=+y[sumBy]+(+x[sumBy])中,我们使用“+”将字符串转换为数字。

function groupBy(data, fields, sumBy='Value') {
  let r=[], cmp= (x,y) => fields.reduce((a,b)=> a && x[b]==y[b], true);
  data.forEach(x=> {
    let y=r.find(z=>cmp(x,z));
    let w= [...fields,sumBy].reduce((a,b) => (a[b]=x[b],a), {})
    y ? y[sumBy]=+y[sumBy]+(+x[sumBy]) : r.push(w);
  });
  return r;
}

常量d=[{阶段:“阶段1”,步骤:“步骤1”,任务:“任务1”,值:“5”},{阶段:“阶段1”,步骤:“步骤1”,任务:“任务2”,值:“10”},{阶段:“阶段1”,步骤:“步骤2”,任务:“任务1”,值:“15”},{阶段:“阶段1”,步骤:“步骤2”,任务:“任务2”,值:“20”},{阶段:“阶段2”,步骤:“步骤1”,任务:“任务1”,值:“25”},{阶段:“阶段2”,步骤:“步骤1”,任务:“任务2”,值:“30”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务1”,值:“35”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务2”,值:“40”}];函数groupBy(数据,字段,sumBy='Value'){设r=[],cmp=(x,y)=>fields.reduce((a,b)=>a&&x[b]==y[b],true);data.forEach(x=>{设y=r.find(z=>cmp(x,z));设w=[…fields,sumBy].reduce((a,b)=>(a[b]=x[b],a),{})yy[sumBy]=+y[sumBy]+(+x[sumBy]):r.push(w);});返回r;}//测试let p=(t,o)=>console.log(t,JSON.stringify(o));console.log('GROUP BY:');p(“相”,组By(d,[“相”]));p(“步骤”,组By(d,[“步骤”]));p(“阶段-步骤”,组By(d,[“阶段”,“步骤”]));p(“阶段任务”,groupBy(d,[“阶段”,“任务”]));p(“步骤任务”,groupBy(d,[“步骤”,“任务”]));p(“阶段-步骤-任务”,groupBy(d,[“阶段”,“步骤”,“任务”]));


通常,我使用Lodash JavaScript实用程序库和预先构建的groupBy()方法。它非常容易使用,请在此处查看更多详细信息。


这里有一个使用ES6的讨厌的、难以阅读的解决方案:

export default (arr, key) => 
  arr.reduce(
    (r, v, _, __, k = v[key]) => ((r[k] || (r[k] = [])).push(v), r),
    {}
  );

对于那些询问这是如何工作的人,这里有一个解释:

在这两个=>中,您可以获得免费回报Array.prototype.reduce函数最多包含4个参数。这就是为什么要添加第五个参数,这样我们就可以使用默认值在参数声明级别为组(k)创建一个廉价的变量声明。(是的,这是巫术)如果我们的当前组在上一次迭代中不存在,我们将创建一个新的空数组((r[k]||(r[k]=[]))。这将计算到最左边的表达式,换句话说,一个现有数组或一个空数组,这就是为什么在该表达式之后会立即推送,因为无论哪种方式都会得到一个数组。当有一个返回时,逗号运算符将丢弃最左边的值,返回该场景中经过调整的前一组。

更容易理解的版本是:

export default (array, key) => 
  array.reduce((previous, currentItem) => {
    const group = currentItem[key];
    if (!previous[group]) previous[group] = [];
    previous[group].push(currentItem);
    return previous;
  }, {});

编辑:

TS版本:

const groupBy = <T, K extends keyof any>(list: T[], getKey: (item: T) => K) =>
  list.reduce((previous, currentItem) => {
    const group = getKey(currentItem);
    if (!previous[group]) previous[group] = [];
    previous[group].push(currentItem);
    return previous;
  }, {} as Record<K, T[]>);

let x  = [
  {
    "id": "6",
    "name": "SMD L13",
    "equipmentType": {
      "id": "1",
      "name": "SMD"
    }
  },
  {
    "id": "7",
    "name": "SMD L15",
    "equipmentType": {
      "id": "1",
      "name": "SMD"
    }
  },
  {
    "id": "2",
    "name": "SMD L1",
    "equipmentType": {
      "id": "1",
      "name": "SMD"
    }
  }
];

function groupBy(array, property) {
  return array.reduce((accumulator, current) => {
    const object_property = current[property];
    delete current[property]

    let classified_element = accumulator.find(x => x.id === object_property.id);
    let other_elements = accumulator.filter(x => x.id !== object_property.id);

   if (classified_element) {
     classified_element.children.push(current)
   } else {
     classified_element = {
       ...object_property, 
       'children': [current]
     }
   }
   return [classified_element, ...other_elements];
 }, [])
}

console.log( groupBy(x, 'equipmentType') )

/* output 

[
  {
    "id": "1",
    "name": "SMD",
    "children": [
      {
        "id": "6",
        "name": "SMD L13"
      },
      {
        "id": "7",
        "name": "SMD L15"
      },
      {
        "id": "2",
        "name": "SMD L1"
      }
    ]
  }
]

*/

我会检查声明性js groupBy,它似乎正符合您的要求。它也是:

非常有性能(性能基准)用打字机书写,所以所有打字都包括在内。不强制使用第三方类似数组的对象。

import { Reducers } from 'declarative-js';
import groupBy = Reducers.groupBy;
import Map = Reducers.Map;

const data = [
    { 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" }
];

data.reduce(groupBy(element=> element.Step), Map());
data.reduce(groupBy('Step'), Map());

使用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)
    }
  })

想象一下,你有这样的东西:

〔{id:1,cat:'sedan'},{id:2,cat:'sport‘},{id:3,cat:'sport‘},{id:4,cat:'sadan‘}〕

通过这样做:const categories=[…new Set(cars.map((car)=>car.cat))]

你会得到这个:[“sadan”,“port”]

说明:1.首先,我们通过传递一个数组来创建一个新的Set。由于Set仅允许唯一值,因此将删除所有重复项。

现在重复项消失了,我们将使用扩展运算符将其转换回数组。。。

设置文档:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set排列运算符文档:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax


function groupBy(array, groupBy){
        return array.reduce((acc,curr,index,array) => {
           var  idx = curr[groupBy]; 
              if(!acc[idx]){
                    acc[idx] = array.filter(item => item[groupBy] === idx)
              } 
            return  acc; 

        },{})
    }

// call
groupBy(items,'Step')

我已经改进了答案。此函数获取组字段数组并返回分组对象,该对象的键也是组字段的对象。

function(xs, groupFields) {
        groupFields = [].concat(groupFields);
        return xs.reduce(function(rv, x) {
            let groupKey = groupFields.reduce((keyObject, field) => {
                keyObject[field] = x[field];
                return keyObject;
            }, {});
            (rv[JSON.stringify(groupKey)] = rv[JSON.stringify(groupKey)] || []).push(x);
            return rv;
        }, {});
    }



let x = [
{
    "id":1,
    "multimedia":false,
    "language":["tr"]
},
{
    "id":2,
    "multimedia":false,
    "language":["fr"]
},
{
    "id":3,
    "multimedia":true,
    "language":["tr"]
},
{
    "id":4,
    "multimedia":false,
    "language":[]
},
{
    "id":5,
    "multimedia":false,
    "language":["tr"]
},
{
    "id":6,
    "multimedia":false,
    "language":["tr"]
},
{
    "id":7,
    "multimedia":false,
    "language":["tr","fr"]
}
]

groupBy(x, ['multimedia','language'])

//{
//{"multimedia":false,"language":["tr"]}: Array(3), 
//{"multimedia":false,"language":["fr"]}: Array(1), 
//{"multimedia":true,"language":["tr"]}: Array(1), 
//{"multimedia":false,"language":[]}: Array(1), 
//{"multimedia":false,"language":["tr","fr"]}: Array(1)
//}

发帖是因为即使这个问题已经7年了,我仍然没有看到一个符合原始标准的答案:

我不希望它们“拆分”,而是“合并”,更像SQL组方法

我最初发表这篇文章是因为我想找到一种方法来减少对象数组(例如,当您从csv中读取时创建的数据结构),并通过给定索引聚合以生成相同的数据结构。我正在寻找的返回值是另一个对象数组,而不是我在这里看到的嵌套对象或映射。

下面的函数获取一个数据集(对象数组)、一个索引列表(数组)和一个reducer函数,并将reducer功能应用于索引的结果作为一个对象数组返回。

function agg(data, indices, reducer) {

  // helper to create unique index as an array
  function getUniqueIndexHash(row, indices) {
    return indices.reduce((acc, curr) => acc + row[curr], "");
  }

  // reduce data to single object, whose values will be each of the new rows
  // structure is an object whose values are arrays
  // [{}] -> {{}}
  // no operation performed, simply grouping
  let groupedObj = data.reduce((acc, curr) => {
    let currIndex = getUniqueIndexHash(curr, indices);

    // if key does not exist, create array with current row
    if (!Object.keys(acc).includes(currIndex)) {
      acc = {...acc, [currIndex]: [curr]}
    // otherwise, extend the array at currIndex
    } else {
      acc = {...acc, [currIndex]: acc[currIndex].concat(curr)};
    }

    return acc;
  }, {})

  // reduce the array into a single object by applying the reducer
  let reduced = Object.values(groupedObj).map(arr => {
    // for each sub-array, reduce into single object using the reducer function
    let reduceValues = arr.reduce(reducer, {});

    // reducer returns simply the aggregates - add in the indices here
    // each of the objects in "arr" has the same indices, so we take the first
    let indexObj = indices.reduce((acc, curr) => {
      acc = {...acc, [curr]: arr[0][curr]};
      return acc;
    }, {});

    reduceValues = {...indexObj, ...reduceValues};


    return reduceValues;
  });


  return reduced;
}

我将创建一个返回count(*)和sum(Value)的reducer:

reducer = (acc, curr) => {
  acc.count = 1 + (acc.count || 0);
  acc.value = +curr.Value + (acc.value|| 0);
  return acc;
}

最后,使用我们的reducer将agg函数应用于原始数据集会生成一个应用了适当聚合的对象数组:

agg(tasks, ["Phase"], reducer);
// yields:
Array(2) [
  0: Object {Phase: "Phase 1", count: 4, value: 50}
  1: Object {Phase: "Phase 2", count: 4, value: 130}
]

agg(tasks, ["Phase", "Step"], reducer);
// yields:
Array(4) [
  0: Object {Phase: "Phase 1", Step: "Step 1", count: 2, value: 15}
  1: Object {Phase: "Phase 1", Step: "Step 2", count: 2, value: 35}
  2: Object {Phase: "Phase 2", Step: "Step 1", count: 2, value: 55}
  3: Object {Phase: "Phase 2", Step: "Step 2", count: 2, value: 75}
]

我不认为给出的答案是对问题的回应,我认为以下内容应回答第一部分:

常量arr=[{阶段:“阶段1”,步骤:“步骤1”,任务:“任务1”,值:“5”},{阶段:“阶段1”,步骤:“步骤1”,任务:“任务2”,值:“10”},{阶段:“阶段1”,步骤:“步骤2”,任务:“任务1”,值:“15”},{阶段:“阶段1”,步骤:“步骤2”,任务:“任务2”,值:“20”},{阶段:“阶段2”,步骤:“步骤1”,任务:“任务1”,值:“25”},{阶段:“阶段2”,步骤:“步骤1”,任务:“任务2”,值:“30”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务1”,值:“35”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务2”,值:“40”}]const groupBy=(key)=>arr.sort((a,b)=>a[key].localeCompare(b[key])).reduce((total,currentValue)=>{const newTotal=总计;如果(总长度&&总计[total.length-1][key]==当前值[key])新总计[total.length-1]={…总计[总长度-1],…当前值,值:parseInt(total[total.length-1].Value)+parseInt(currentValue.Value,};else newTotal[total.length]=当前值;return newTotal;}, []);console.log(groupBy(“阶段”));//=>[{阶段:“阶段1”,值:50},{阶段“阶段2”,值130}]console.log(groupBy(“步骤”));//=>[{步骤:“步骤1”,值:70},{步骤“步骤2”,值为110}]


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;
}, []);

GroupBy one liner,ES2021解决方案

const groupBy = (x,f)=>x.reduce((a,b,i)=>((a[f(b,i,x)]||=[]).push(b),a),{});

TypeScript(类型脚本)

const groupBy = <T>(array: T[], predicate: (value: T, index: number, array: T[]) => string) =>
  array.reduce((acc, value, index, array) => {
    (acc[predicate(value, index, array)] ||= []).push(value);
    return acc;
  }, {} as { [key: string]: T[] });

示例

const groupBy = (x,f)=>x.reduce((a,b,i)=>((a[f(b,i,x)]||=[]).push(b),a),{});
// f -> should must return string/number because it will be use as key in object

// for demo

groupBy([1, 2, 3, 4, 5, 6, 7, 8, 9], v => (v % 2 ? "odd" : "even"));
// { odd: [1, 3, 5, 7, 9], even: [2, 4, 6, 8] };
const colors = [
  "Apricot",
  "Brown",
  "Burgundy",
  "Cerulean",
  "Peach",
  "Pear",
  "Red",
];

groupBy(colors, v => v[0]); // group by colors name first letter
// {
//   A: ["Apricot"],
//   B: ["Brown", "Burgundy"],
//   C: ["Cerulean"],
//   P: ["Peach", "Pear"],
//   R: ["Red"],
// };
groupBy(colors, v => v.length); // group by length of color names
// {
//   3: ["Red"],
//   4: ["Pear"],
//   5: ["Brown", "Peach"],
//   7: ["Apricot"],
//   8: ["Burgundy", "Cerulean"],
// }

const data = [
  { comment: "abc", forItem: 1, inModule: 1 },
  { comment: "pqr", forItem: 1, inModule: 1 },
  { comment: "klm", forItem: 1, inModule: 2 },
  { comment: "xyz", forItem: 1, inModule: 2 },
];

groupBy(data, v => v.inModule); // group by module
// {
//   1: [
//     { comment: "abc", forItem: 1, inModule: 1 },
//     { comment: "pqr", forItem: 1, inModule: 1 },
//   ],
//   2: [
//     { comment: "klm", forItem: 1, inModule: 2 },
//     { comment: "xyz", forItem: 1, inModule: 2 },
//   ],
// }

groupBy(data, x => x.forItem + "-" + x.inModule); // group by module with item
// {
//   "1-1": [
//     { comment: "abc", forItem: 1, inModule: 1 },
//     { comment: "pqr", forItem: 1, inModule: 1 },
//   ],
//   "1-2": [
//     { comment: "klm", forItem: 1, inModule: 2 },
//     { comment: "xyz", forItem: 1, inModule: 2 },
//   ],
// }

按映射分组

const groupByToMap = (x, f) =>
  x.reduce((a, b, i, x) => {
    const k = f(b, i, x);
    a.get(k)?.push(b) ?? a.set(k, [b]);
    return a;
  }, new Map());

TypeScript(类型脚本)

const groupByToMap = <T, Q>(array: T[], predicate: (value: T, index: number, array: T[]) => Q) =>
  array.reduce((map, value, index, array) => {
    const key = predicate(value, index, array);
    map.get(key)?.push(value) ?? map.set(key, [value]);
    return map;
  }, new Map<Q, T[]>());

/***数组分组依据*@类别数组*@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>


使用ES6的简单解决方案:

该方法有一个返回模型,可以比较n个财产。

const compareKey = (item, key, compareItem) => {
    return item[key] === compareItem[key]
}

const handleCountingRelatedItems = (listItems, modelCallback, compareKeyCallback) => {
    return listItems.reduce((previousValue, currentValue) => {
        if (Array.isArray(previousValue)) {
        const foundIndex = previousValue.findIndex(item => compareKeyCallback(item, currentValue))

        if (foundIndex > -1) {
            const count = previousValue[foundIndex].count + 1

            previousValue[foundIndex] = modelCallback(currentValue, count)

            return previousValue
        }

        return [...previousValue, modelCallback(currentValue, 1)]
        }

        if (compareKeyCallback(previousValue, currentValue)) {
        return [modelCallback(currentValue, 2)]
        }

        return [modelCallback(previousValue, 1), modelCallback(currentValue, 1)]
    })
}

const itemList = [
    { type: 'production', human_readable: 'Production' },
    { type: 'test', human_readable: 'Testing' },
    { type: 'production', human_readable: 'Production' }
]

const model = (currentParam, count) => ({
    label: currentParam.human_readable,
    type: currentParam.type,
    count
})

const compareParameter = (item, compareValue) => {
    const isTypeEqual = compareKey(item, 'type', compareValue)
    return isTypeEqual
}

const result = handleCountingRelatedItems(itemList, model, compareParameter)

 console.log('Result: \n', result)
/** Result: 
    [
        { label: 'Production', type: 'production', count: 2 },
        { label: 'Testing', type: 'testing', count: 1 }
    ]
*/

在Joseph Nields的回答之后,有一个polyfill用于将对象分组https://github.com/padcom/array-prototype-functions#arrayprototypegroupbyfieldormapper.因此,您可能希望使用现有的内容,而不是一次又一次地编写这些内容。


在我的特定用例中,我需要按属性分组,然后删除分组属性。

无论如何,该属性只是为了分组目的而添加到记录中的,对于向用户显示它没有意义。

    group (arr, key) {

        let prop;

        return arr.reduce(function(rv, x) {
            prop = x[key];
            delete x[key];
            (rv[prop] = (rv[prop] || [])).push(x);
            return rv;
        }, {});

    },

顶部答案中的起始函数归功于@caesar bautista。


让我们在重用已经编写的代码(即Undercore)的同时充分回答最初的问题。如果将Undercore的>100个功能组合在一起,您可以使用Undercore做得更多。下面的解决方案演示了这一点。

步骤1:按财产的任意组合对数组中的对象进行分组。这使用了一个事实,即_.groupBy接受一个返回对象组的函数。它还使用了_.schain、_.pick、_.values、_.jjoin和_.value。请注意,这里并不严格需要_.value,因为链接的值在用作属性名称时会自动展开。我加入它是为了防止在没有自动展开的情况下有人试图编写类似的代码时发生混淆。

// Given an object, return a string naming the group it belongs to.
function category(obj) {
    return _.chain(obj).pick(propertyNames).values().join(' ').value();
}

// Perform the grouping.
const intermediate = _.groupBy(arrayOfObjects, category);

给定原始问题中的arrayOfObjects并将propertyNames设置为['Phase','Step'],intermediate将获得以下值:

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

步骤2:将每个组缩减为单个平面对象,并将结果返回到数组中。除了我们之前看到的函数之外,下面的代码使用了_.pulse、..first、..pick、..extend、..reduce和..map。.first保证在这种情况下返回一个对象,因为..groupBy不会产生空组。在这种情况下,值是必需的。

// Sum two numbers, even if they are contained in strings.
const addNumeric = (a, b) => +a + +b;

// Given a `group` of objects, return a flat object with their common
// properties and the sum of the property with name `aggregateProperty`.
function summarize(group) {
    const valuesToSum = _.pluck(group, aggregateProperty);
    return _.chain(group).first().pick(propertyNames).extend({
        [aggregateProperty]: _.reduce(valuesToSum, addNumeric)
    }).value();
}

// Get an array with all the computed aggregates.
const result = _.map(intermediate, summarize);

给定我们之前获得的中间值,并将aggregateProperty设置为Value,我们得到了询问者想要的结果:

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

我们可以将所有这些放在一个函数中,该函数将arrayOfObjects、propertyNames和aggregateProperty作为参数。请注意,arrayOfObjects实际上也可以是带有字符串键的普通对象,因为_.groupBy接受其中之一。为此,我将arrayOfObjects重命名为collection。

function aggregate(collection, propertyNames, aggregateProperty) {
    function category(obj) {
        return _.chain(obj).pick(propertyNames).values().join(' ');
    }
    const addNumeric = (a, b) => +a + +b;
    function summarize(group) {
        const valuesToSum = _.pluck(group, aggregateProperty);
        return _.chain(group).first().pick(propertyNames).extend({
            [aggregateProperty]: _.reduce(valuesToSum, addNumeric)
        }).value();
    }
    return _.chain(collection).groupBy(category).map(summarize).value();
}

aggregate(arrayOfObjects,['Phase','Step'],'Value')现在将再次为我们提供相同的结果。

我们可以更进一步,使调用者能够计算每个组中值的任何统计信息。我们可以这样做,也可以让调用者向每个组的摘要添加任意财产。我们可以在缩短代码的同时完成所有这些。我们用iteratee参数替换aggregateProperty参数,并将其直接传递给_.reduce:

function aggregate(collection, propertyNames, iteratee) {
    function category(obj) {
        return _.chain(obj).pick(propertyNames).values().join(' ');
    }
    function summarize(group) {
        return _.chain(group).first().pick(propertyNames)
            .extend(_.reduce(group, iteratee)).value();
    }
    return _.chain(collection).groupBy(category).map(summarize).value();
}

实际上,我们将部分责任转移给了来电者;她必须提供一个可以传递给.reduce的iterate,这样对.reduce的调用将生成一个包含她想要添加的聚合财产的对象。例如,我们使用以下表达式获得与之前相同的结果:

aggregate(arrayOfObjects, ['Phase', 'Step'], (memo, value) => ({
    Value: +memo.Value + +value.Value
}));

对于一个稍微复杂一点的迭代的示例,假设我们希望计算每个组的最大值而不是总和,并且我们希望添加一个列出组中出现的Task的所有值的Tasks属性。这里有一种方法可以做到这一点,使用上面最后一个版本的聚合(和_.union):

aggregate(arrayOfObjects, ['Phase', 'Step'], (memo, value) => ({
    Value: Math.max(memo.Value, value.Value),
    Tasks: _.union(memo.Tasks || [memo.Task], [value.Task])
}));

我们得到以下结果:

[
    { Phase: "Phase 1", Step: "Step 1", Value: 10, Tasks: [ "Task 1", "Task 2" ] },
    { Phase: "Phase 1", Step: "Step 2", Value: 20, Tasks: [ "Task 1", "Task 2" ] },
    { Phase: "Phase 2", Step: "Step 1", Value: 30, Tasks: [ "Task 1", "Task 2" ] },
    { Phase: "Phase 2", Step: "Step 2", Value: 40, Tasks: [ "Task 1", "Task 2" ] }
]

归功于@much2learn,他还发布了一个可以处理任意缩减函数的答案。我又写了几个SO答案,演示了如何通过组合多个Undercore函数来实现复杂的功能:

https://stackoverflow.com/a/64938636/1166087https://stackoverflow.com/a/64094738/1166087https://stackoverflow.com/a/63625129/1166087https://stackoverflow.com/a/63088916/1166087


groupBy函数,可以通过特定键或给定的分组函数对数组进行分组。键入。

groupBy = <T, K extends keyof T>(array: T[], groupOn: K | ((i: T) => string)): Record<string, T[]> => {
  const groupFn = typeof groupOn === 'function' ? groupOn : (o: T) => o[groupOn];

  return Object.fromEntries(
    array.reduce((acc, obj) => {
      const groupKey = groupFn(obj);
      return acc.set(groupKey, [...(acc.get(groupKey) || []), obj]);
    }, new Map())
  ) as Record<string, T[]>;
};

现在有点晚了,但也许有人喜欢这个。

ES6:

常量用户=[{name:“Jim”,颜色:“蓝色”},{name:“Sam”,颜色:“蓝色”},{name:“Eddie”,颜色:“绿色”},{姓名:“Robert”,颜色:“绿色”},];常量groupBy=(arr,key)=>{const initialValue={};返回arr.reduce((acc,cval)=>{const myAttribute=cval[key];acc[myAttribute]=[…(acc[myAattribute]| |[]),cval]返回acc;},initialValue);};const res=groupBy(用户,“颜色”);console.log(“分组依据:”,res);


您可以使用本机JavaScript组数组方法(目前处于第3阶段)。

我认为,与reduce相比,或者与lodash等第三方库相比,解决方案要优雅得多。

常量产品=[{名称:“牛奶”,类型:“乳制品”},{名称:“cheese”,类型:“乳制品”},{名称:“牛肉”,类型:“肉”},{名称:“chicken”,类型:“肉”}];const productsByType=products.group((product)=>product.type);console.log(“按类型分组的产品:”,productsByType);<script src=“https://cdn.jsdelivr.net/npm/core-js-bundle@3.23.2/minified.min.js“></script>


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


    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"
            }
        ]
    }
};


解释相同的代码。喜欢它我喜欢这里

const groupBy = (array, key) => {
  return array.reduce((result, currentValue) => {
    (result[currentValue[key]] = result[currentValue[key]] || []).push(
      currentValue
    );
    console.log(result);
    return result;
  }, {});
};

USE

 let group =   groupBy(persons, 'color');

这是一个基于TS的功能,不是性能最好的,但很容易阅读和理解!

function groupBy<T>(array: T[], key: string): Record<string, T[]> {
const groupedObject = {}
for (const item of array) {
  const value = item[key]
    if (groupedObject[value] === undefined) {
  groupedObject[value] = []
  }
  groupedObject[value].push(item)
}
  return groupedObject
}

我们以->

const data = [
{ 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" },
];
console.log(groupBy(data, 'Step'))
{
'Step 1': [
    {
      Phase: 'Phase 1',
      Step: 'Step 1',
      Task: 'Task 1',
      Value: '5'
    },
    {
      Phase: 'Phase 1',
      Step: 'Step 1',
      Task: 'Task 2',
      Value: '10'
    }
  ],
  'Step 2': [
    {
      Phase: 'Phase 1',
      Step: 'Step 2',
      Task: 'Task 1',
      Value: '15'
    },
    {
      Phase: 'Phase 1',
      Step: 'Step 2',
      Task: 'Task 2',
      Value: '20'
    }
  ]
}

常量动物=[{type:'狗',品种:“水坑”},{type:'狗',品种:“拉布拉多”},{类型:'cat',品种:“暹罗”},{type:'狗',品种:“法国斗牛犬”},{类型:'cat',品种:'泥'}];var groupBy=(arr,prop)=>{return arr.reduce((objs,obj)=>{const key=obj[prop];if(键){让fi=objs.findIndex(x=>x.key==key);如果(fi>=0){objs[fi].values=[…objs[fi].values,obj];}其他{对象.推送({key:键,值:[obj]})}}返回对象;}, []);}console.log(groupBy(动物,“类型”))