javascript 如何对数据项进行分组,并通过对相关值进行求和来合并它们的属性?

wgx48brx  于 2023-09-29  发布在  Java
关注(0)|答案(3)|浏览(87)

给定的是一个数据项数组,其中需要对每个属性的值进行总计,其中每个属性的键与另外提供的属性名(用作分组键)不同。
因此,对于分组密钥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,
}]

是否有一种方法或参考,使一个生产的输出像上面的一个?

balp4ylt

balp4ylt1#

从我上面的评论…

  • “我投票反对关闭上述Q。重复,因为没有一个单一的答案,上述评论的链接线程确实提供了一个完全通用的解决方案(这是可能实现的),以OP的具体问题。到目前为止提供的答案都是朝着这个方向发展的,其中一个是 * 先生。Polywhirl* 是完全通用的,而 Alexandria 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,
  }
}

...同时还能够利用nullish coalescing assignment operator / ??=来创建不存在的组的初始数据或访问已经存在的组的数据。
返回到当前处理的数组项,它的每个属性名称都是未知的。
在这里,我们利用了destructuring assignments,比如对象解构和rest属性。由于所提供的分组键名称已作为key提供并分配给变量key,,因此需要计算属性名称的语法。因此,每个属性名未知的当前处理的数组项必须像...

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

...其中变量keyValue保存name-keys的值,而restData是一个对象,它的特征是每隔一个键值对(条目),而不是基于name-key的键值对。
至于OP的第一个数据项...

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

. keyValue被分配了"Name1"restData引用了.

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

为了计算一个组的每个同名属性值的总量,需要迭代每个restData对象的entriesrestDataFor each键-值对(再次在无效合并赋值的帮助下)或者最初创建基于数据键的属性并将零/0赋值给它,或者/并且通过当前数据值对已经存在的属性的值求和(总计)。
OP请求的最终数据结构通过将返回的result对象传递给Object.values来实现。

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>
tnkciper

tnkciper2#

您可以通过键值将每个对象缩减为一个贴图。对于每一项,循环键并忽略key参数。
将传入的item[k]existing[k]项相加。
最后,返回Mapvalues(),并将迭代器扩展为新的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; }
fzsnzjdm

fzsnzjdm3#

使用Array::reduce()构造一个新数组,记住map中的元素名称,并将它们保存到结果数组中,然后使用map用新的总数更新元素:

const result = arr.reduce((r, {name, ...item}) => {
  const found = r.map.get(name);
  if(found){
    Object.keys(item).forEach(key => found[key] += item[key]);
  } else {
    r.map.set(name, r.arr[r.arr.length] = {name, ...item});  
  }
  return r;
}, {arr: [], map: new Map}).arr;

console.log(result);
<script>
const arr = [
{
  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>

相关问题