You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Reduce将对象数组聚合为指定结构的对象或数组?

问题描述

我有如下对象数组:

const inputArray = [
  { name: "sam", date: "1 / 1 / 23", confirmed: "yes", spent: 0 },
  { name: "sam", date: "1 / 2 / 23", confirmed: "yes", spent: 4 },
  { name: "sam", date: "1 / 3 / 23", confirmed: "yes", spent: 4 },
  { name: "sam", date: "1 / 4 / 23", confirmed: "no", spent: 4 },
  { name: "bill", date: "1 / 5 / 23", confirmed: "yes", spent: 4 },
  { name: "bill", date: "1 / 6 / 23", confirmed: "yes", spent: 4 },
  { name: "bill", date: "1 / 5 / 23", confirmed: "yes", spent: 0 },
  { name: "annie", date: "1 / 6 / 23", confirmed: "yes", spent: 0 },
  { name: "annie", date: "1 / 6 / 23", confirmed: "no", spent: 2 },
  { name: "annie", date: "1 / 6 / 23", confirmed: "no", spent: 2 },
];

需要生成满足以下要求的输出对象:

  • spent字段为对应name的所有spent值之和
  • confirmedAndNotSpent字段为对应name中confirmed为'yes'且spent为0的记录数
  • notConfirmedAndSpent字段为对应name中confirmed为'no'的记录数

期望的对象格式输出如下:

const outputObj = {
  sam: { spent: 12, confirmedAndNotSpent: 1, notConfirmedAndSpent: 1 },
  bill: { spent: 8, confirmedAndNotSpent: 1, notConfirmedAndSpent: 0 },
  annie: { spent: 4, confirmedAndNotSpent: 1, notConfirmedAndSpent: 2 },
};

我尝试了以下代码:

let try1 = inputArray.reduce((accumulator, current) => {
  if (!accumulator[current.name]) accumulator[current.name] = 0;
  accumulator[current.name] += +current.spent;
  return accumulator;
}, {});

该代码得到输出:{ sam: 12, bill: 8, annie: 4 },但无法扩展为目标结构。尝试的第二段代码:

let try2 = inputArray.reduce((accumulator, current) => {
  if (!accumulator[current.name]) accumulator[current.name] = {};
  accumulator[current.name][current.spent] += +current.spent;
  return accumulator;
}, {})

得到输出:

{
  sam: { '0': NaN, '4': NaN },
  bill: { '0': NaN, '4': NaN },
  annie: { '0': NaN, '2': NaN }
}

请问如何实现目标输出?


补充:另一种输出格式

后续还需要生成如下数组格式的输出:

const outputObj2 = [
  { name: "sam", spent: 12, confirmedAndNotSpent: 1, notConfirmedAndSpent: 1 },
  { name: "bill", spent: 8, confirmedAndNotSpent: 1, notConfirmedAndSpent: 0 },
  { name: "annie", spent: 4, confirmedAndNotSpent: 1, notConfirmedAndSpent: 2 },
];

我尝试了以下代码得到正确输出,但不确定是否为最优实现,希望得到改进建议:

let try3 = inputArray.reduce((accumulator, current) => {
  if (!accumulator.some((accumulator) => accumulator.name === current.name)) {
    let obj = {
      name: current.name,
      spent: current.spent,
      confirmedAndNotSpent:
        current.confirmed === "yes" && current.spent === 0 ? 1 : 0,
      notConfirmedAndSpent: current.confirmed === "no" ? 1 : 0,
    };
    accumulator.push(obj);
  } else {
    //Find index of specific object using findIndex method.
    objIndex = accumulator.findIndex((obj) => obj.name == current.name);

    //update the specific object using index
    accumulator[objIndex].spent += current.spent;

    if (current.confirmed === "yes" && current.spent === 0) {
      accumulator[objIndex].confirmedAndNotSpent++;
    }

    if (current.confirmed === "no") {
      accumulator[objIndex].notConfirmedAndSpent++;
    }
  }

  return accumulator;
}, []);

解决方案

一、生成对象格式的输出

可以通过reduce方法,为每个用户初始化包含三个目标字段的对象,再遍历每条记录更新对应字段:

const outputObj = inputArray.reduce((acc, curr) => {
  // 若当前用户不存在,初始化统计对象
  if (!acc[curr.name]) {
    acc[curr.name] = {
      spent: 0,
      confirmedAndNotSpent: 0,
      notConfirmedAndSpent: 0
    };
  }
  // 更新总花费
  acc[curr.name].spent += curr.spent;
  // 更新confirmedAndNotSpent计数
  if (curr.confirmed === 'yes' && curr.spent === 0) {
    acc[curr.name].confirmedAndNotSpent++;
  }
  // 更新notConfirmedAndSpent计数
  if (curr.confirmed === 'no') {
    acc[curr.name].notConfirmedAndSpent++;
  }
  return acc;
}, {});

这段代码的逻辑是:

  • 遍历数组时,先检查累加器中是否已有当前用户的统计对象,没有则初始化三个字段为0
  • 每次遍历都累加spent值
  • 根据当前记录的confirmed和spent值,判断是否需要增加对应计数字段的值

二、生成数组格式的输出(优化现有实现)

你当前的try3代码虽然能得到正确结果,但每次判断用户是否存在时用some,更新时又用findIndex,这两个方法都会遍历数组,时间复杂度是O(n²),当数据量较大时效率会降低。

优化方案1:先统计再转数组

先用reduce生成对象格式的统计结果,再将对象转换为数组,时间复杂度为O(n),效率更高:

// 先生成对象格式的统计结果
const tempObj = inputArray.reduce((acc, curr) => {
  if (!acc[curr.name]) {
    acc[curr.name] = {
      name: curr.name,
      spent: 0,
      confirmedAndNotSpent: 0,
      notConfirmedAndSpent: 0
    };
  }
  acc[curr.name].spent += curr.spent;
  if (curr.confirmed === 'yes' && curr.spent === 0) {
    acc[curr.name].confirmedAndNotSpent++;
  }
  if (curr.confirmed === 'no') {
    acc[curr.name].notConfirmedAndSpent++;
  }
  return acc;
}, {});

// 将对象转换为数组
const outputObj2 = Object.values(tempObj);

优化方案2:一次reduce完成,用映射表避免重复遍历

维护一个映射表(对象)来记录用户在数组中的索引,避免重复遍历数组:

const outputObj2 = inputArray.reduce(({ arr, map }, curr) => {
  if (!map[curr.name]) {
    const newItem = {
      name: curr.name,
      spent: 0,
      confirmedAndNotSpent: 0,
      notConfirmedAndSpent: 0
    };
    arr.push(newItem);
    map[curr.name] = arr.length - 1; // 记录新元素的索引
  }
  const index = map[curr.name];
  arr[index].spent += curr.spent;
  if (curr.confirmed === 'yes' && curr.spent === 0) {
    arr[index].confirmedAndNotSpent++;
  }
  if (curr.confirmed === 'no') {
    arr[index].notConfirmedAndSpent++;
  }
  return { arr, map };
}, { arr: [], map: {} }).arr;

这段代码通过map对象存储每个用户对应的数组索引,每次处理记录时直接通过索引找到对应元素更新,避免了some和findIndex的重复遍历,效率更高。


内容的提问来源于stack exchange,提问作者moys

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 15:45:23