新闻中心

J*aScript reduce 方法实现复杂对象数组的嵌套转换与数据聚合

2025-11-11
浏览次数:
返回列表

javascript reduce 方法实现复杂对象数组的嵌套转换与数据聚合

本文详细阐述如何利用 J*aScript `reduce` 方法将扁平化的对象数组转换成具有多级嵌套结构的数据。通过以 `medico`、`rateio` 和 `convenio` 为键进行分组,并对 `subtotal` 值进行累加,本教程展示了 `reduce` 在复杂数据重塑和聚合场景中的强大功能与实现细节,提供清晰的代码示例和实践建议。

引言:复杂数据转换的需求

在前端或后端开发中,我们经常会遇到需要将扁平化的数据结构转换为更具层次感的嵌套结构,并在此过程中进行数据聚合的需求。例如,将一系列包含医生、分摊方案和合同信息以及子总额的记录,转换为按医生、分摊方案、合同层层分组并汇总子总额的报表形式。传统上,这可以通过多层循环实现,但 J*aScript 的 Array.prototype.reduce() 方法提供了一种更函数式、更简洁且通常更高效的解决方案。

考虑以下原始数据结构:

const arr = [
  { medico: "med1", rateio: "rat1", convenio: "conv1", subtotal: 10 },
  { medico: "med2", rateio: "rat2", convenio: "conv2", subtotal: 10 },
  { medico: "med2", rateio: "rat2", convenio: "conv2", subtotal: 20 },
  { medico: "med1", rateio: "rat1", convenio: "conv3", subtotal: 20 },
  { medico: "med1", rateio: "rat1", convenio: "conv3", subtotal: 25 },
  { medico: "med2", rateio: "rat3", convenio: "conv4", subtotal: 15 },
  { medico: "med2", rateio: "rat4", convenio: "conv3", subtotal: 10 },
];

我们期望将其转换为以下嵌套聚合结构:

const result = [
  {
    medico: "med1",
    grantotals: [
      {
        rateio: "rat1",
        grandtotals: [
          { convenio: "conv1", sum_subtotal: 10 },
          { convenio: "conv3", sum_subtotal: 45 },
        ],
      },
    ],
  },
  {
    medico: "med2",
    grantotals: [
      {
        rateio: "rat2",
        grandtotals: [{ convenio: "conv2", sum_subtotal: 30 }],
      },
      {
        rateio: "rat3",
        grandtotals: [{ convenio: "conv4", sum_subtotal: 15 }],
      },
      {
        rateio: "rat4",
        grandtotals: [{ convenio: "conv3", sum_subtotal: 10 }],
      },
    ],
  },
];

Array.prototype.reduce() 核心概念

reduce() 方法对数组中的每个元素执行一个由您提供的 reducer 函数,将其结果汇总为单个返回值。它接收两个主要参数:

  1. reducer 函数:一个回调函数,包含四个参数:
    • accumulator (累加器):回调函数累计处理的结果。
    • currentValue (当前值):数组中正在处理的当前元素。
    • currentIndex (当前索引,可选):数组中正在处理的当前元素的索引。
    • array (原数组,可选):reduce 被调用的数组。
  2. initialValue (初始值,可选):作为第一次调用 reducer 函数时的 accumulator 值。如果未提供,则 accumulator 将使用数组的第一个元素,并且 currentValue 将从第二个元素开始。在构建复杂结构时,通常强烈建议提供一个合适的 initialValue(例如,空数组 [] 或空对象 {})。

在本场景中,我们将利用 reduce 的累加器来逐步构建目标嵌套结构。

Tanka Tanka

具备AI长期记忆的下一代团队协作沟通工具

Tanka 146 查看详情 Tanka

分层聚合逻辑详解

使用 reduce 方法实现上述转换的核心在于,在遍历原始数组的每个元素时,根据 medico、rateio 和 convenio 的值,在累加器中查找或创建对应的嵌套层级,并对 subtotal 进行聚合。

  1. 初始化累加器 我们将 reduce 的初始值设为一个空数组 []。这个空数组将作为最终结果的顶层结构,存储按 medico 分组的对象。

    const result = arr.reduce((acc, obj) => {
      // ... 逻辑 ...
      return acc;
    }, []); // 初始累加器为 []
  2. 第一层分组:按 medico 分组 对于 arr 中的每一个 obj,我们首先在累加器 acc 中查找是否存在 medico 值与 obj.medico 相同的对象。

    • 如果找到 existingMedico:说明该医生已存在于结果中,我们继续处理其内部的 grantotals。
    • 如果未找到:说明这是一个新的医生,我们需要在 acc 中添加一个新的 medico 对象,并初始化其 grantotals 数组,其中包含当前 obj 对应的 rateio 和 convenio 结构。
    const existingMedico = acc.find((item) => item.medico === obj.medico);
    
    if (existingMedico) {
      // 医生已存在,处理 rateio 层级
    } else {
      // 新医生,创建新的 medico 对象及初始结构
      acc.push({
        medico: obj.medico,
        grantotals: [
          {
            rateio: obj.rateio,
            grandtotals: [
              {
                convenio: obj.convenio,
                sum_subtotal: obj.subtotal,
              },
            ],
          },
        ],
      });
    }
  3. 第二层分组:按 rateio 分组 如果 medico 已存在 (existingMedico 不为空),我们接着在其 grantotals 数组中查找是否存在 rateio 值与 obj.rateio 相同的对象。

    • 如果找到 existingRateio:说明该分摊方案已存在于当前医生的记录中,我们继续处理其内部的 grandtotals。
    • 如果未找到:说明这是一个新的分摊方案,我们需要在 existingMedico.grantotals 中添加一个新的 rateio 对象,并初始化其 grandtotals 数组,其中包含当前 obj 对应的 convenio 结构。
    if (existingMedico) {
      const existingRateio = existingMedico.grantotals.find(
        (item) => item.rateio === obj.rateio
      );
    
      if (existingRateio) {
        // 分摊方案已存在,处理 convenio 层级
      } else {
        // 新的分摊方案,创建新的 rateio 对象及初始结构
        existingMedico.grantotals.push({
          rateio: obj.rateio,
          grandtotals: [
            {
              convenio: obj.convenio,
              sum_subtotal: obj.subtotal,
            },
          ],
        });
      }
    }
  4. 第三层分组与聚合:按 convenio 分组并累加 subtotal 如果 rateio 也已存在 (existingRateio 不为空),我们接着在其 grandtotals 数组中查找是否存在 convenio 值与 obj.convenio 相同的对象。

    • 如果找到 existingConvenio:说明该合同已存在于当前分摊方案中,我们直接将 obj.subtotal 累加到 existingConvenio.sum_subtotal。
    • 如果未找到:说明这是一个新的合同,我们需要在 existingRateio.grandtotals 中添加一个新的 convenio 对象,并初始化 sum_subtotal 为 obj.subtotal。
    if (existingRateio) {
      const existingConvenio = existingRateio.grandtotals.find(
        (item) => item.convenio === obj.convenio
      );
    
      if (existingConvenio) {
        // 合同已存在,累加 subtotal
        existingConvenio.sum_subtotal += obj.subtotal;
      } else {
        // 新合同,创建新的 convenio 对象
        existingRateio.grandtotals.push({
          convenio: obj.convenio,
          sum_subtotal: obj.subtotal,
        });
      }
    }

完整代码示例

将上述逻辑整合,形成完整的 reduce 实现:

const arr = [
  { medico: "med1", rateio: "rat1", convenio: "conv1", subtotal: 10 },
  { medico: "med2", rateio: "rat2", convenio: "conv2", subtotal: 10 },
  { medico: "med2", rateio: "rat2", convenio: "conv2", subtotal: 20 },
  { medico: "med1", rateio: "rat1", convenio: "conv3", subtotal: 20 },
  { medico: "med1", rateio: "rat1", convenio: "conv3", subtotal: 25 },
  { medico: "med2", rateio: "rat3", convenio: "conv4", subtotal: 15 },
  { medico: "med2", rateio: "rat4", convenio: "conv3", subtotal: 10 },
];

const result = arr.reduce((acc, obj) => {
  // 查找是否存在当前 medico
  const existingMedico = acc.find((item) => item.medico === obj.medico);

  if (existingMedico) {
    // 如果 medico 存在,查找是否存在当前 rateio
    const existingRateio = existingMedico.grantotals.find(
      (item) => item.rateio === obj.rateio
    );

    if (existingRateio) {
      // 如果 rateio 存在,查找是否存在当前 convenio
      const existingConvenio = existingRateio.grandtotals.find(
        (item) => item.convenio === obj.convenio
      );

      if (existingConvenio) {
        // 如果 convenio 存在,累加 subtotal
        existingConvenio.sum_subtotal += obj.subtotal;
      } else {
        // 如果 convenio 不存在,添加新的 convenio 对象
        existingRateio.grandtotals.push({
          convenio: obj.convenio,
          sum_subtotal: obj.subtotal,
        });
      }
    } else {
      // 如果 rateio 不存在,添加新的 rateio 对象及其初始 convenio
      existingMedico.grantotals.push({
        rateio: obj.rateio,
        grandtotals: [
          {
            convenio: obj.convenio,
            sum_subtotal: obj.subtotal,
          },
        ],
      });
    }
  } else {
    // 如果 medico 不存在,添加新的 medico 对象及其初始 rateio 和 convenio
    acc.push({
      medico: obj.medico,
      grantotals: [
        {
          rateio: obj.rateio,
          grandtotals: [
            {
              convenio: obj.convenio,
              sum_subtotal: obj.subtotal,
            },
          ],
        },
      ],
    });
  }

  return acc; // 返回累加器
}, []); // 初始累加器为一个空数组

console.log(JSON.stringify(result, null, 2)); // 打印格式化后的结果

注意事项与优化建议

  1. 代码可读性与维护性 虽然 reduce 提供了强大的功能,但多层嵌套的 find 调用可能会降低代码的可读性,尤其是在层级更深的情况下。为了提高可读性,可以考虑将内部的查找和创建逻辑封装成辅助函数。

  2. 性能考量 在上述解决方案中,每次迭代都需要在累加器内部的数组中进行 find 操作。find 方法的平均时间复杂度为 O(N),其中 N 是被搜索数组的长度。如果原始数组 arr 包含大量数据,并且每个 medico 或 rateio 下的子项也很多,那么这种重复的 find 操作可能导致整体时间复杂度接近 O(N^2),从而影响性能。

    优化方案:使用 Map 进行 O(1) 查找 为了提高查找效率,可以使用 J*aScript 的 Map 对象来存储中间结果。Map 提供了 O(1) 的平均时间复杂度进行键值查找。

    基本思路是:

    • 使用一个 Map 来存储 medico 对象,键为 medico 名称。
    • 在每个 medico 对象内部,再使用一个 Map 来存储 rateio 对象,键为 rateio 名称。
    • 在每个 rateio 对象内部,再使用一个 Map 来存储 convenio 对象,键为 convenio 名称。
    • 最后将 Map 的值转换为数组。

    这将显著提高处理大规模数据的性能。

  3. 数据变异与纯函数 本教程中的 reduce 实现直接修改了累加器 acc 及其内部对象的属性(例如 existingConvenio.sum_subtotal += obj.subtotal)。这种直接修改是 reduce 在构建复杂结构时的一种常见且有效的方法。然而,在强调纯函数式编程和不可变性的场景中,可能需要通过创建新对象(例如使用展开运算符 ...)来避免直接修改原始数据或累加器,但这通常会增加代码的复杂性。对于大多数数据转换场景,本示例中的方法是完全可接受的。

总结

Array.prototype.reduce() 方法是 J*aScript 中一个极其强大的工具,尤其适用于将数组转换或聚合为新的、更复杂的数据结构。通过本教程,我们学习了如何利用 reduce 结合多层查找和条件逻辑,将扁平化的对象数组成功转换为按多级键分组并聚合数值的嵌套结构。理解其工作原理和潜在的性能考量,并根据实际项目需求选择合适的优化策略,将使您能够更高效、更优雅地处理复杂的数据转换任务。

以上就是J*aScript reduce 方法实现复杂对象数组的嵌套转换与数据聚合的详细内容,更多请关注其它相关文章!


# 转换为  # 钻石模型网站推广  # 学习seo怎么样  # 朔州seo公司到1火星  # 宣武优化排名seo  # 企业的推广网站有哪些  # 剧目推广营销  # 汤阴seo推广电话  # 吕梁正规seo优化  # 安康推广营销招聘网  # 铁岭seo服务平台电话  # 可选  # 不存在  # 如何实现  # 这是一个  # 组中  # javascript  # 是否存在  # 回调  # 数据结构  # 累加器  # red  # 代码可读性  # 后端开发  # 后端  # 工具  # 回调函数  # json  # 前端  # js  # java 


相关栏目: 【 科技资讯46185 】 【 网络学院92790


相关推荐: React中useState与局部变量:理解组件状态管理与渲染机制  三星GalaxyZFold5怎样在相册制作折叠屏分镜_iPhone三星GalaxyZFold5相册制作折叠屏分镜【创意编辑】  PHP中SSG-WSG API的AES加密实践:正确使用初始化向量  PS5 Pro有点优势但不多! 《燕云十六声》PS5平台与PC性能画面对比  护手霜蹭到袖口上了如何清洗? 怎样避免留下一圈油印?  如何在Promise链中有效终止错误处理后的执行  C++如何操作大型数据集_使用C++流式处理(Streaming)技术避免一次性加载大文件  铁路12306官网网页端快速入口 铁路12306官方首页登录教程  Win10桌面图标出现小盾牌怎么办 Win10去除UAC图标教程【解决】  如何使 Jest 模拟函数默认抛出错误以提高测试效率  ACG动漫手机版官网入口 手机ACG动漫APP在线观看正版  html5 app怎么运行环境_配html5 app运行环境【教程】  AWS EC2实例间SQL Server连接超时:安全组配置与故障排除指南  厨房不锈钢水槽发黑生锈怎么处理_水槽用可乐+锡纸2分钟抛亮如新  composer 和 npm/yarn 在管理依赖方面有什么核心思想差异?  Node.js中HTML按钮与J*aScript函数交互的正确姿势  Python大型XML文件高效流式解析教程  Golang如何实现Web接口签名验证_Golang Web接口签名校验开发方法  Win10如何清理注册表垃圾 Win10注册表维护与优化指南【慎用】  C++如何进行游戏物理模拟_使用Box2D库为C++游戏添加2D物理效果  优化LangChain文档加载与ChromaDB集成:解决多文档处理与分块问题  J*aScript中高效清空DOM列表元素:解决for循环中断与任务管理问题  Win11怎么用U盘重装系统 Win11制作启动盘并重装系统完整教程【详解】  在Go语言中利用后缀数组处理多字符串:实现高效文本匹配与自动补全  Excel如何用迷你图显趋势_Excel用迷你图显趋势【趋势小图】  荒野行动PC版怎么注册_荒野行动PC版账号注册详细流程图文教程  C++如何解决segmentation fault_C++段错误调试与原因分析  Composer如何解决json扩展缺失的错误  优化MinIO list_objects_v2 操作的性能瓶颈与最佳实践  Gmail邮箱申请注册直达_Gmail邮箱免费注册PC版官网入口2025  ArchiveofOurOwn小说阅读-ArchiveofOurOwn同人作品访问链接  Windows电脑怎么截图最方便_系统自带截图工具的5种神仙用法【技巧】  必由学官方平台入口 必由学在线课堂登录地址  Lar*el Form Request中唯一性验证在更新操作中的正确实现  优化HTML表单样式:解决输入框焦点跳动与元素间距问题  Win10磁盘清理工具在哪 Win10打开并使用磁盘清理【教程】  QQ邮箱电脑版登录入口_QQ邮箱官方网站登录平台  J*a应用集成GitHub CLI与API认证指南  TikTok网页版直接登录 TikTok网页端官方平台入口  win11如何卸载Windows更新补丁 Win11解决更新导致系统不稳定的问题【修复】  美团外卖商家服务中心入口 美团商家版官网入口  蛙漫官网漫画入口地址_蛙漫在线畅读无广告弹窗  PHP中高效并行检查多链接状态的教程  Win10双系统截图高效法 截屏快捷键速记【技巧】  age动漫网站入口 age动漫官网直接访问入口  单射、满射与双射的关系 一文理清所有逻辑  MongoDB Aggregation:在嵌套对象数组中精确匹配ObjectId  三星ZFold5多任务卡顿_Samsung ZFold5流畅度提升  Win11蓝牙耳机断连怎么解决 Win11蓝牙设置重新配对与驱动更新【技巧】  漫蛙官网正版漫画入口 漫蛙2官方网页登录地址 

搜索