如何按键对一组对象进行分组?
Posted
技术标签:
【中文标题】如何按键对一组对象进行分组?【英文标题】:How can I group an array of objects by key? 【发布时间】:2017-04-08 01:36:03 【问题描述】:有没有人知道一种方法(如果可能的话,lodash)通过对象键对对象数组进行分组,然后根据分组创建一个新的对象数组?例如,我有一个汽车对象数组:
const cars = [
'make': 'audi',
'model': 'r8',
'year': '2012'
,
'make': 'audi',
'model': 'rs5',
'year': '2013'
,
'make': 'ford',
'model': 'mustang',
'year': '2012'
,
'make': 'ford',
'model': 'fusion',
'year': '2015'
,
'make': 'kia',
'model': 'optima',
'year': '2012'
,
];
我想创建一个按make
分组的新汽车对象数组:
const cars =
'audi': [
'model': 'r8',
'year': '2012'
,
'model': 'rs5',
'year': '2013'
,
],
'ford': [
'model': 'mustang',
'year': '2012'
,
'model': 'fusion',
'year': '2015'
],
'kia': [
'model': 'optima',
'year': '2012'
]
【问题讨论】:
您的结果无效。 是否有类似的方法来获取地图而不是对象? 如果您使用的是 Typescript(这不是 OP 的情况),您已经拥有 groupBy 方法。您可以使用your_array.groupBy(...)
your_array.groupBy(...) 不存在!!
【参考方案1】:
在纯 javascript 中,您可以将 Array#reduce
与对象一起使用
var cars = [ make: 'audi', model: 'r8', year: '2012' , make: 'audi', model: 'rs5', year: '2013' , make: 'ford', model: 'mustang', year: '2012' , make: 'ford', model: 'fusion', year: '2015' , make: 'kia', model: 'optima', year: '2012' ],
result = cars.reduce(function (r, a)
r[a.make] = r[a.make] || [];
r[a.make].push(a);
return r;
, Object.create(null));
console.log(result);
.as-console-wrapper max-height: 100% !important; top: 0;
【讨论】:
如何迭代result
结果?
您可以使用Object.entries
获取条目并遍历键/值对。
有没有办法在分组后从数据集中删除make
?它占用了额外的空间。
一如既往的最佳答案。你为什么要传递 Object.create(null)?
@Alex,请看这里:***.com/questions/38068424/…【参考方案2】:
Timo's answer 我会这样做。简单_.groupBy
,并允许分组结构中的对象有一些重复。
但是,OP 还要求删除重复的 make
键。如果你想一路走下去:
var grouped = _.mapValues(_.groupBy(cars, 'make'),
clist => clist.map(car => _.omit(car, 'make')));
console.log(grouped);
产量:
audi:
[ model: 'r8', year: '2012' ,
model: 'rs5', year: '2013' ],
ford:
[ model: 'mustang', year: '2012' ,
model: 'fusion', year: '2015' ],
kia:
[ model: 'optima', year: '2012' ]
如果您想使用 Underscore.js 执行此操作,请注意其版本的 _.mapValues
称为 _.mapObject
。
【讨论】:
【参考方案3】:您正在寻找_.groupBy()
。
如果需要,从对象中删除您分组的属性应该很简单:
const cars = [
'make': 'audi',
'model': 'r8',
'year': '2012'
,
'make': 'audi',
'model': 'rs5',
'year': '2013'
,
'make': 'ford',
'model': 'mustang',
'year': '2012'
,
'make': 'ford',
'model': 'fusion',
'year': '2015'
,
'make': 'kia',
'model': 'optima',
'year': '2012'
];
const grouped = _.groupBy(cars, car => car.make);
console.log(grouped);
<script src='https://cdn.jsdelivr.net/lodash/4.17.2/lodash.min.js'></script>
【讨论】:
如果你想让它更短,var grouped = _.groupBy(cars, 'make');
根本不需要函数,如果访问器是一个简单的属性名称。
“_”代表什么?
@AdrianGrzywaczewski 这是名称间距“lodash”或“下划线”的默认约定。现在图书馆是模块化的,它不再需要,即。 npmjs.com/package/lodash.groupby
我怎样才能对结果进行交互?
我相信这将与 Object.keys(grouped)【参考方案4】:
完全没有理由下载 3rd 方库来解决这个简单的问题,就像上面的解决方案所建议的那样。
在 es6 中通过某个 key
对对象的 list
进行分组的单行版本:
const groupByKey = (list, key) => list.reduce((hash, obj) => (...hash, [obj[key]]:( hash[obj[key]] || [] ).concat(obj)), )
过滤掉没有key
的对象的较长版本:
function groupByKey(array, key)
return array
.reduce((hash, obj) =>
if(obj[key] === undefined) return hash;
return Object.assign(hash, [obj[key]]:( hash[obj[key]] || [] ).concat(obj))
, )
var cars = ['make':'audi','model':'r8','year':'2012','make':'audi','model':'rs5','year':'2013','make':'ford','model':'mustang','year':'2012','make':'ford','model':'fusion','year':'2015','make':'kia','model':'optima','year':'2012'];
console.log(groupByKey(cars, 'make'))
注意:最初的问题似乎是询问如何按品牌对汽车进行分组,但在每个组中都省略了品牌。因此,没有 3rd 方库的简短答案如下所示:
const groupByKey = (list, key, omitKey=false) => list.reduce((hash, [key]:value, ...rest) => (...hash, [value]:( hash[value] || [] ).concat(omitKey ? ...rest : [key]:value, ...rest) ), )
var cars = ['make':'audi','model':'r8','year':'2012','make':'audi','model':'rs5','year':'2013','make':'ford','model':'mustang','year':'2012','make':'ford','model':'fusion','year':'2015','make':'kia','model':'optima','year':'2012'];
console.log(groupByKey(cars, 'make', omitKey:true))
【讨论】:
这绝对不是es5 它的工作原理!谁能详细说明这个reduce函数? 我喜欢您的两个答案,但我看到它们都提供“make”字段作为每个“make”数组的成员。我已经根据您的答案提供了一个答案,其中交付的输出与预期的输出相匹配。谢谢!【参考方案5】:这是您自己的 groupBy
函数,它是对以下代码的概括:https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore
function groupBy(xs, f)
return xs.reduce((r, v, i, a, k = f(v)) => ((r[k] || (r[k] = [])).push(v), r), );
const cars = [ make: 'audi', model: 'r8', year: '2012' , make: 'audi', model: 'rs5', year: '2013' , make: 'ford', model: 'mustang', year: '2012' , make: 'ford', model: 'fusion', year: '2015' , make: 'kia', model: 'optima', year: '2012' ];
const result = groupBy(cars, (c) => c.make);
console.log(result);
【讨论】:
我喜欢这个答案,因为您也可以使用嵌套属性 - 非常好。我刚刚为 Typescript 更改了它,这正是我想要的 :)【参考方案6】:var cars = [
make: 'audi',
model: 'r8',
year: '2012'
,
make: 'audi',
model: 'rs5',
year: '2013'
,
make: 'ford',
model: 'mustang',
year: '2012'
,
make: 'ford',
model: 'fusion',
year: '2015'
,
make: 'kia',
model: 'optima',
year: '2012'
].reduce((r, car) =>
const
model,
year,
make
= car;
r[make] = [...r[make] || [],
model,
year
];
return r;
, );
console.log(cars);
【讨论】:
需要紧急帮助如何在上面存储,例如 [ "audi": [ "model": "r8", "year": "2012" ] , "ford": [ "model": "r9", "year": "2021" ] ...] 每个对象【参考方案7】:也可以使用简单的for
循环:
const result = ;
for(const make, model, year of cars)
if(!result[make]) result[make] = [];
result[make].push( model, year );
【讨论】:
而且可能更快,更简单。我已将您的 sn-p 扩展为更具动态性,因为我不想输入 db 表中的一长串字段。另请注意,您需要将 const 替换为 let。for ( let TABLE_NAME, ...fields of source) result[TABLE_NAME] = result[TABLE_NAME] || []; result[TABLE_NAME].push( ...fields );
直到,谢谢! medium.com/@mautayro/…【参考方案8】:
对于 JS 数组示例,我将 REAL GROUP BY
与此任务完全相同 here
const inputArray = [
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"
];
var outObject = inputArray.reduce(function(a, e)
// GROUP BY estimated key (estKey), well, may be a just plain key
// a -- Accumulator result object
// e -- sequentally checked Element, the Element that is tested just at this itaration
// new grouping name may be calculated, but must be based on real value of real field
let estKey = (e['Phase']);
(a[estKey] ? a[estKey] : (a[estKey] = null || [])).push(e);
return a;
, );
console.log(outObject);
【讨论】:
【参考方案9】:您可以尝试修改 _.groupBy 函数每次迭代调用的函数内的对象。 注意源数组改变了他的元素!
var res = _.groupBy(cars,(car)=>
const makeValue=car.make;
delete car.make;
return makeValue;
)
console.log(res);
console.log(cars);
【讨论】:
虽然这段代码可以解决问题,including an explanation 解决问题的方式和原因确实有助于提高帖子的质量。请记住,您正在为将来的读者回答问题,而不仅仅是现在提问的人!请编辑您的答案以添加解释,并说明适用的限制和假设。 这对我来说似乎是最好的答案,因为您只需遍历数组一次即可获得所需的结果。无需使用其他函数来删除make
属性,而且它也更具可读性。【参考方案10】:
创建一个可以重复使用的方法
Array.prototype.groupBy = function(prop)
return this.reduce(function(groups, item)
const val = item[prop]
groups[val] = groups[val] || []
groups[val].push(item)
return groups
, )
;
然后你可以在下面按任何标准分组
const groupByMake = cars.groupBy('make');
console.log(groupByMake);
var cars = [
'make': 'audi',
'model': 'r8',
'year': '2012'
,
'make': 'audi',
'model': 'rs5',
'year': '2013'
,
'make': 'ford',
'model': 'mustang',
'year': '2012'
,
'make': 'ford',
'model': 'fusion',
'year': '2015'
,
'make': 'kia',
'model': 'optima',
'year': '2012'
,
];
//re-usable method
Array.prototype.groupBy = function(prop)
return this.reduce(function(groups, item)
const val = item[prop]
groups[val] = groups[val] || []
groups[val].push(item)
return groups
, )
;
// initiate your groupBy. Notice the recordset Cars and the field Make....
const groupByMake = cars.groupBy('make');
console.log(groupByMake);
//At this point we have objects. You can use Object.keys to return an array
【讨论】:
【参考方案11】:对于 key 可以为 null 的情况,我们希望将它们分组为 others
var cars = ['make':'audi','model':'r8','year':'2012','make':'audi','model':'rs5','year':'2013','make':'ford','model':'mustang','year':'2012','make':'ford','model':'fusion','year':'2015','make':'kia','model':'optima','year':'2012',
'make':'kia','model':'optima','year':'2033',
'make':null,'model':'zen','year':'2012',
'make':null,'model':'blue','year':'2017',
];
result = cars.reduce(function (r, a)
key = a.make || 'others';
r[key] = r[key] || [];
r[key].push(a);
return r;
, Object.create(null));
【讨论】:
【参考方案12】:只需简单的 forEach 循环即可在此处运行,无需任何库
var cars = [
'make': 'audi',
'model': 'r8',
'year': '2012'
,
'make': 'audi',
'model': 'rs5',
'year': '2013'
,
'make': 'ford',
'model': 'mustang',
'year': '2012'
,
'make': 'ford',
'model': 'fusion',
'year': '2015'
,
'make': 'kia',
'model': 'optima',
'year': '2012'
,
];
let ObjMap =;
cars.forEach(element =>
var makeKey = element.make;
if(!ObjMap[makeKey])
ObjMap[makeKey] = [];
ObjMap[makeKey].push(
model: element.model,
year: element.year
);
);
console.log(ObjMap);
【讨论】:
最优雅易读的解决方案【参考方案13】:同意除非您经常使用这些,否则不需要外部库。尽管有类似的解决方案可用,但如果您想了解正在发生的事情,我发现其中一些解决方案很难遵循here is a gist,该解决方案与 cmets 有一个解决方案。
const cars = [
'make': 'audi',
'model': 'r8',
'year': '2012'
,
'make': 'audi',
'model': 'rs5',
'year': '2013'
,
'make': 'ford',
'model': 'mustang',
'year': '2012'
,
'make': 'ford',
'model': 'fusion',
'year': '2015'
,
'make': 'kia',
'model': 'optima',
'year': '2012'
, ];
/**
* Groups an array of objects by a key an returns an object or array grouped by provided key.
* @param array - array to group objects by key.
* @param key - key to group array objects by.
* @param removeKey - remove the key and it's value from the resulting object.
* @param outputType - type of structure the output should be contained in.
*/
const groupBy = (
inputArray,
key,
removeKey = false,
outputType = ,
) =>
return inputArray.reduce(
(previous, current) =>
// Get the current value that matches the input key and remove the key value for it.
const
[key]: keyValue
= current;
// remove the key if option is set
removeKey && keyValue && delete current[key];
// If there is already an array for the user provided key use it else default to an empty array.
const
[keyValue]: reducedValue = []
= previous;
// Create a new object and return that merges the previous with the current object
return Object.assign(previous,
[keyValue]: reducedValue.concat(current)
);
,
// Replace the object here to an array to change output object to an array
outputType,
);
;
console.log(groupBy(cars, 'make', true))
【讨论】:
【参考方案14】:试试这个对我来说效果很好。
let grouped = _.groupBy(cars, 'make');
注意:使用 lodash 库,所以包含它。
【讨论】:
Uncaught ReferenceError: _ is not defined - 你应该清楚你的解决方案需要安装一个第三方库来解决这个问题。 对不起,我想每个人都知道。 _ 代表并主要用于 lodash lib。所以你需要使用 lodash。请阅读问题,这样您就会知道他/她在要求 lodash。好吧,谢谢。我会记住这一点。永远不要忘记写 lib。 你应该编辑你的答案以包括你正在使用一个库。【参考方案15】:原型版本也使用 ES6。 基本上,这使用reduce函数传入一个累加器和当前项,然后使用它根据传入的键构建“分组”数组。 reduce 的内部部分可能看起来很复杂,但本质上它是在测试传入对象的键是否存在,如果不存在则创建一个空数组并将当前项附加到新创建的数组,否则使用展开运算符传入当前键数组的所有对象并附加当前项。希望这对某人有帮助!。
Array.prototype.groupBy = function(k)
return this.reduce((acc, item) => ((acc[item[k]] = [...(acc[item[k]] || []), item]), acc),);
;
const projs = [
project: "A",
timeTake: 2,
desc: "this is a description"
,
project: "B",
timeTake: 4,
desc: "this is a description"
,
project: "A",
timeTake: 12,
desc: "this is a description"
,
project: "B",
timeTake: 45,
desc: "this is a description"
];
console.log(projs.groupBy("project"));
【讨论】:
【参考方案16】:另一种解决方案:
var cars = [
'make': 'audi','model': 'r8','year': '2012', 'make': 'audi','model': 'rs5','year': '2013',
'make': 'ford','model': 'mustang','year': '2012', 'make': 'ford','model': 'fusion','year': '2015',
'make': 'kia','model': 'optima','year': '2012',
];
const reducedCars = cars.reduce((acc, make, model, year ) => (
...acc,
[make]: acc[make] ? [ ...acc[make], model, year ] : [ model, year ],
), );
console.log(reducedCars);
【讨论】:
【参考方案17】:你也可以像这样使用array#forEach()
方法:
const cars = [ make: 'audi', model: 'r8', year: '2012' , make: 'audi', model: 'rs5', year: '2013' , make: 'ford', model: 'mustang', year: '2012' , make: 'ford', model: 'fusion', year: '2015' , make: 'kia', model: 'optima', year: '2012' ];
let newcars =
cars.forEach(car =>
newcars[car.make] ? // check if that array exists or not in newcars object
newcars[car.make].push(model: car.model, year: car.year) // just push
: (newcars[car.make] = [], newcars[car.make].push(model: car.model, year: car.year)) // create a new array and push
)
console.log(newcars);
【讨论】:
【参考方案18】:function groupBy(data, property)
return data.reduce((acc, obj) =>
const key = obj[property];
if (!acc[key])
acc[key] = [];
acc[key].push(obj);
return acc;
, );
groupBy(people, 'age');
【讨论】:
【参考方案19】:我喜欢写它,没有依赖/复杂性,只是纯粹的简单 js。
const mp =
const cars = [
model: 'Imaginary space craft SpaceX model',
year: '2025'
,
make: 'audi',
model: 'r8',
year: '2012'
,
make: 'audi',
model: 'rs5',
year: '2013'
,
make: 'ford',
model: 'mustang',
year: '2012'
,
make: 'ford',
model: 'fusion',
year: '2015'
,
make: 'kia',
model: 'optima',
year: '2012'
]
cars.forEach(c =>
if (!c.make) return // exit (maybe add them to a "no_make" category)
if (!mp[c.make]) mp[c.make] = [ model: c.model, year: c.year ]
else mp[c.make].push( model: c.model, year: c.year )
)
console.log(mp)
【讨论】:
【参考方案20】:我喜欢@metakunfu 的回答,但它并没有完全提供预期的输出。 这是一个在最终 JSON 有效负载中摆脱“make”的更新。
var cars = [
'make': 'audi',
'model': 'r8',
'year': '2012'
,
'make': 'audi',
'model': 'rs5',
'year': '2013'
,
'make': 'ford',
'model': 'mustang',
'year': '2012'
,
'make': 'ford',
'model': 'fusion',
'year': '2015'
,
'make': 'kia',
'model': 'optima',
'year': '2012'
,
];
result = cars.reduce((h, car) => Object.assign(h, [car.make]:( h[car.make] || [] ).concat(model: car.model, year: car.year) ), )
console.log(JSON.stringify(result));
输出:
"audi":[
"model":"r8",
"year":"2012"
,
"model":"rs5",
"year":"2013"
],
"ford":[
"model":"mustang",
"year":"2012"
,
"model":"fusion",
"year":"2015"
],
"kia":[
"model":"optima",
"year":"2012"
]
【讨论】:
【参考方案21】:使用 lodash/fp,您可以使用 _.flow()
创建一个函数,该函数首先按一个键分组,然后映射每个组,并从每个项目中省略一个键:
const flow, groupBy, mapValues, map, omit = _;
const groupAndOmitBy = key => flow(
groupBy(key),
mapValues(map(omit(key)))
);
const cars = [ make: 'audi', model: 'r8', year: '2012' , make: 'audi', model: 'rs5', year: '2013' , make: 'ford', model: 'mustang', year: '2012' , make: 'ford', model: 'fusion', year: '2015' , make: 'kia', model: 'optima', year: '2012' ];
const groupAndOmitMake = groupAndOmitBy('make');
const result = groupAndOmitMake(cars);
console.log(result);
.as-console-wrapper max-height: 100% !important; top: 0;
<script src='https://cdn.jsdelivr.net/g/lodash@4(lodash.min.js+lodash.fp.min.js)'></script>
【讨论】:
【参考方案22】:打字稿中的对象分组数组:
groupBy (list: any[], key: string): Map<string, Array<any>>
let map = new Map();
list.map(val=>
if(!map.has(val[key]))
map.set(val[key],list.filter(data => data[key] == val[key]));
);
return map;
);
【讨论】:
当您搜索每个键时,这看起来效率低下。搜索的复杂度很可能是 O(n)。 使用 Typescript 你已经有了 groupBy 方法。您可以使用your_array.groupBy(...)
【参考方案23】:
我做了一个基准测试来测试每个不使用外部库的解决方案的性能。
JSBen.ch
@Nina Scholz 发布的reduce()
选项似乎是最佳选项。
【讨论】:
【参考方案24】:添加了Array.prototype.groupBy
和Array.prototype.groupByToMap
的A proposal 现在处于第3 阶段!
当它达到第 4 阶段并在大多数主流浏览器上实现时,您将能够做到这一点:
const cars = [
make: 'audi', model: 'r8', year: '2012' ,
make: 'audi', model: 'rs5', year: '2013' ,
make: 'ford', model: 'mustang', year: '2012' ,
make: 'ford', model: 'fusion', year: '2015' ,
make: 'kia', model: 'optima', year: '2012'
];
const grouped = cars.groupBy(item => item.make);
console.log(grouped);
这将输出:
audi: [
make: 'audi', model: 'r8', year: '2012' ,
make: 'audi', model: 'rs5', year: '2013'
],
ford: [
make: 'ford', model: 'mustang', year: '2012' ,
make: 'ford', model: 'fusion', year: '2015'
],
kia: [
make: 'kia', model: 'optima', year: '2012'
]
在那之前,你可以使用this core-js polyfill:
const cars = [
make: 'audi', model: 'r8', year: '2012' ,
make: 'audi', model: 'rs5', year: '2013' ,
make: 'ford', model: 'mustang', year: '2012' ,
make: 'ford', model: 'fusion', year: '2015' ,
make: 'kia', model: 'optima', year: '2012'
];
const grouped = cars.groupBy(item => item.make);
//console.log(grouped);
// Optional: remove the "make" property from resulting object
const entriesUpdated = Object
.entries(grouped)
.map(([key, value]) => [
key,
value.map((make, ...rest) => rest)
]);
const noMake = Object.fromEntries(entriesUpdated);
console.log(noMake);
<script src="https://unpkg.com/core-js-bundle@3.20.1/minified.js"></script>
【讨论】:
【参考方案25】:如果您不想输入所有字段,请在 @Jonas_Wilms 的 answer 上构建:
var result = ;
for ( let first_field, ...fields of your_data )
result[first_field] = result[first_field] || [];
result[first_field].push( ...fields );
我没有进行任何基准测试,但我相信使用 for 循环也会比 this answer 中建议的任何方法更有效。
【讨论】:
for...of 循环效率最低。为 forEach 循环使用 for(i...)【参考方案26】:const reGroup = (list, key) =>
const newGroup = ;
list.forEach(item =>
const newItem = Object.assign(, item);
delete newItem[key];
newGroup[item[key]] = newGroup[item[key]] || [];
newGroup[item[key]].push(newItem);
);
return newGroup;
;
const animals = [
type: 'dog',
breed: 'puddle'
,
type: 'dog',
breed: 'labradoodle'
,
type: 'cat',
breed: 'siamese'
,
type: 'dog',
breed: 'french bulldog'
,
type: 'cat',
breed: 'mud'
];
console.log(reGroup(animals, 'type'));
const cars = [
'make': 'audi',
'model': 'r8',
'year': '2012'
,
'make': 'audi',
'model': 'rs5',
'year': '2013'
,
'make': 'ford',
'model': 'mustang',
'year': '2012'
,
'make': 'ford',
'model': 'fusion',
'year': '2015'
,
'make': 'kia',
'model': 'optima',
'year': '2012'
,
];
console.log(reGroup(cars, 'make'));
【讨论】:
【参考方案27】:这是一个受 Java 中的 Collectors.groupingBy() 启发的解决方案:
function groupingBy(list, keyMapper)
return list.reduce((accummalatorMap, currentValue) =>
const key = keyMapper(currentValue);
if(!accummalatorMap.has(key))
accummalatorMap.set(key, [currentValue]);
else
accummalatorMap.set(key, accummalatorMap.get(key).push(currentValue));
return accummalatorMap;
, new Map());
这将给出一个 Map 对象。
// Usage
const carMakers = groupingBy(cars, car => car.make);
【讨论】:
【参考方案28】:这是另一个解决方案。根据要求。
我想创建一个按 make 分组的新汽车对象数组:
function groupBy()
const key = 'make';
return cars.reduce((acc, x) => (
...acc,
[x[key]]: (!acc[x[key]]) ? [
model: x.model,
year: x.year
] : [...acc[x[key]],
model: x.model,
year: x.year
]
), )
输出:
console.log('Grouped by make key:',groupBy())
【讨论】:
以上是关于如何按键对一组对象进行分组?的主要内容,如果未能解决你的问题,请参考以下文章