提问人:Jiji G2A 提问时间:9/27/2023 最后编辑:Peter SeligerJiji G2A 更新时间:9/28/2023 访问量:97
如何对数据项进行分组,并通过汇总相关值来合并其属性?
How to group data-items, and merge their properties by summing up related values?
问:
给定是一个数据项数组,其中需要对每个属性的值进行总和,其中其键与额外提供的属性名称(用作分组键)不同。
因此,对于分组键和示例数据,例如...name
[{
name: "Name1",
amt: 100,
tax: 10,
total: 110,
}, {
name: "Name2",
amt: 50,
tax: 5,
total: 55,
}, {
name: "Name1",
amt: 70,
tax: 7,
total: 77,
}]
...预期结果是......
[{
name: "Name1",
amt: 170,
tax: 17,
total: 187,
}, {
name: "Name2",
amt: 50,
tax: 5,
total: 55,
}]
有没有一种方法或参考可以产生像上面这样的输出?
答:
您可以按键值将每个对象简化为映射。对于每个项目,遍历键并忽略参数。key
将传入的输入求和到条目上。item[k]
existing[k]
最后,返回 并将迭代器分散到一个新对象中。values()
Map
Array
const reduceBy = (data, key) =>
[...data.reduce((map, item) => {
const existing = map.get(item[key]) ?? { [key]: item[key] };
for (let k in item) {
if (k !== key) {
existing[k] = (existing[k] ?? 0) + item[k];
}
}
return map.set(item[key], existing);
}, new Map).values()];
const
input = [
{ name: "Name1", amt: 100, tax: 10, total: 110 },
{ name: "Name2", amt: 50, tax: 5, total: 55 },
{ name: "Name1", amt: 70, tax: 7, total: 77 }
],
expected = [
{ name: "Name1", amt: 170, tax: 17, total: 187 },
{ name: "Name2", amt: 50, tax: 5, total: 55 }
],
actual = reduceBy(input, 'name');
console.log(JSON.stringify(actual) === JSON.stringify(expected));
.as-console-wrapper { top: 0; max-height: 100% !important; }
评论
从我上面的评论......
“我投票反对将上述问题作为重复关闭,因为上述评论的链接线程中没有一个答案确实为 OP 的具体问题提供了一个完全通用的解决方案(这是可能的)。到目前为止提供的答案都朝着这个方向发展,Polywhirl 先生的答案是完全通用的,而 Alexander Nenashev 的答案在大多数情况下是通用的。
可以以完全通用的方式实现 reduce 方法的 reducer 回调函数。
为了提供分组键的名称,需要将对象作为初始值传递给方法的第二个参数。reduce
这个物体可能看起来像那样......
{ key: 'name', result: {} }
...并且还充当累加器/收集器对象,该对象在每个迭代步骤中传递。任何实现都会将最终数据结构聚合到收集器的初始空对象上。选择对象的优势在于以可读的通用数据格式提供最终结果,例如......result
{
Name1: {
name: "Name1",
amt: 170,
tax: 17,
total: 187,
},
Name2: {
name: "Name2",
amt: 50,
tax: 5,
total: 55,
}
}
...同时还能够利用无效合并赋值运算符 / ??=
用于创建不存在的组的初始数据或访问已存在组的数据。
回到当前处理的数组项,其每个属性名称都是未知的。
在这里,人们确实利用了解构赋值,如对象解构和 rest 属性。而且,由于提供的分组键名称已作为变量提供并分配给变量,因此需要计算属性名称的语法。因此,每个当前处理的数组项(其每个属性名称未知)都必须像...key
key,
{ [key]: keyValue, ...restData }
...其中变量 hold 是 -keys 的值,并且是一个对象,它具有与基于 -key 的键值对(条目)不同的所有其他键值对(条目)。keyValue
name
restData
name
至于OP的第一个数据项......
{
name: "Name1",
amt: 100,
tax: 10,
total: 110,
}
... 被分配并确实参考了......keyValue
"Name1"
restData
{
amt: 100,
tax: 10,
total: 110,
}
为了计算组中每个同名属性值的总量,需要迭代每个对象的条目
。对于每个键值对,一个(再次借助无效合并赋值)要么最初创建基于数据键的属性并为其赋值零/,要么/和其中一个按当前数据值对现有属性的值求和(总计)。restData
restData
0
OP 请求的最终数据结构是通过将返回的对象传递给 Object.values
来实现的。result
function groupByKeyAndTotalAnyOtherValue(
// - the destructured accumulator/collector object.
{ key = "", result = {} },
// - the destructured currently processed array-item
// which is unknown by each of its property names.
{ [key]: keyValue, ...restData },
) {
// - access an existing group or create the minimum group data.
const group = result[keyValue] ??= { [key]: keyValue };
Object
// - get all entries of the unknown rest data.
.entries(restData)
.forEach(([dataKey, dataValue]) =>
// - compute the total amount of each of
// a group's equally named property value.
group[dataKey] = (group[dataKey] ??= 0) + dataValue
);
// - pass-on/return the accumulator's/collector's current state.
return { key, result };
}
console.log(
'totaled values as nested object ...',
sampleData
.reduce(
groupByKeyAndTotalAnyOtherValue, { key: 'name', result: {} }
)
.result
);
console.log(
'totaled values as array of objects ...',
Object
.values(
sampleData
.reduce(
groupByKeyAndTotalAnyOtherValue, { key: 'name', result: {} }
)
.result
)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
const sampleData = [{
name: "Name1",
amt: 100,
tax: 10,
total: 110,
}, {
name: "Name2",
amt: 50,
tax: 5,
total: 55,
}, {
name: "Name1",
amt: 70,
tax: 7,
total: 77,
}];
</script>
评论
Array.prototype.reduce