如何对数据项进行分组,并通过汇总相关值来合并其属性?

How to group data-items, and merge their properties by summing up related values?

提问人:Jiji G2A 提问时间:9/27/2023 最后编辑:Peter SeligerJiji G2A 更新时间:9/28/2023 访问量:97

问:

给定是一个数据项数组,其中需要对每个属性的值进行总和,其中其键与额外提供的属性名称(用作分组键)不同。

因此,对于分组键和示例数据,例如...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,
}]

有没有一种方法或参考可以产生像上面这样的输出?

javascript 数组 数据结构 合并 reduce

评论

0赞 Dai 9/27/2023
Array.prototype.reduce
1赞 Heretic Monkey 9/27/2023
这回答了你的问题吗?对对象数组中具有相同对象属性 B 的 JavaScript 对象属性 A 值求和
0赞 Peter Seliger 9/28/2023
我投票反对将上述问题作为重复关闭,因为上述评论的链接线程中没有一个答案确实为 OP 的特定问题提供了完全通用的解决方案(这是可能的)。到目前为止提供的答案都朝着这个方向发展,Polywhirl 先生的答案是完全通用的,而 Alexander Nenashev 的答案在大多数情况下是通用的。
0赞 Peter Seliger 10/17/2023
@JijiG2A......关于到目前为止提供的答案/方法,还有什么问题吗?

答:

-1赞 Mr. Polywhirl 9/27/2023 #1

您可以按键值将每个对象简化为映射。对于每个项目,遍历键并忽略参数。key

将传入的输入求和到条目上。item[k]existing[k]

最后,返回 并将迭代器分散到一个新对象中。values()MapArray

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

评论

1赞 Heretic Monkey 9/27/2023
所有 JavaScript“分组依据”问题都已得到解答......
0赞 Peter Seliger 9/28/2023 #2

从我上面的评论......

“我投票反对将上述问题作为重复关闭,因为上述评论的链接线程中没有一个答案确实为 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 属性。而且,由于提供的分组键名称已作为变量提供并分配给变量,因此需要计算属性名称的语法。因此,每个当前处理的数组项(其每个属性名称未知)都必须像...keykey,

{ [key]: keyValue, ...restData }

...其中变量 hold 是 -keys 的值,并且是一个对象,它具有与基于 -key 的键值对(条目)不同的所有其他键值对(条目)。keyValuenamerestDataname

至于OP的第一个数据项......

{
  name: "Name1",
  amt: 100,
  tax: 10,
  total: 110,
}

... 被分配并确实参考了......keyValue"Name1"restData

{
  amt: 100,
  tax: 10,
  total: 110,
}

为了计算组中每个同名属性值的总量,需要迭代每个对象的条目对于每个键值对,一个(再次借助无效合并赋值)要么最初创建基于数据键的属性并为其赋值零/,要么/和其中一个按当前数据值对现有属性的值求和(总计)。restDatarestData0

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>