0

0

高效重排数组:基于父子关系与显示优先级的复杂排序策略

聖光之護

聖光之護

发布时间:2025-09-17 08:50:11

|

341人浏览过

|

来源于php中文网

原创

高效重排数组:基于父子关系与显示优先级的复杂排序策略

本文旨在提供一个全面的教程,详细讲解如何根据元素的父子关系(id与reference_id)和显示优先级(display_priority)对复杂数组进行重排序。我们将通过构建数据索引、组织父子关系、分步应用排序规则,最终生成一个结构清晰、符合预期的有序数组。

引言:理解复杂数组重排序需求

前端后端开发中,我们经常会遇到需要对数据列表进行复杂排序的场景。其中一种常见的需求是,数据项之间存在层级关系(例如,父子关系),同时每个数据项还有自身的显示优先级。我们的目标是将子项紧随其父项之后显示,并且在同级(包括顶层父项和同一父项下的子项)之间,需要根据display_priority进行升序排列

考虑以下原始数据结构,它是一个包含多个对象的数组,每个对象有id、name、reference_id和display_priority字段:

const rawData = [
  { id: 3, name: 'hello world', reference_id: null, display_priority: 10},
  { id: 6, name: 'hello world', reference_id: 2 , display_priority: 30},
  { id: 1, name: 'hello world', reference_id: 2, display_priority: 40 },
  { id: 4, name: 'hello world', reference_id: null, display_priority: 80},
  { id: 2, name: 'hello world', reference_id: null, display_priority: 100 },
  { id: 5, name: 'hello world', reference_id: 3, display_priority: 110 },
];

我们期望的输出结果应遵循以下规则:

  1. 如果一个元素的reference_id为null,它是一个顶层父项。
  2. 如果一个元素的reference_id不为null,它是一个子项,其父项的id与reference_id匹配。
  3. 子项必须紧随其父项之后。
  4. 所有顶层父项之间,以及同一父项下的所有子项之间,都应根据display_priority进行升序排列。

根据上述规则,期望的输出如下:

[
  { id: 3, name: 'hello world', reference_id: null, display_priority: 10 }, // 父项
  { id: 5, name: 'hello world', reference_id: 3, display_priority: 110 }, // 子项,display_priority: 110
  { id: 4, name: 'hello world', reference_id: null, display_priority: 80 }, // 父项
  { id: 2, name: 'hello world', reference_id: null, display_priority: 100 }, // 父项
  { id: 6, name: 'hello world', reference_id: 2, display_priority: 30}, // 子项,display_priority: 30
  { id: 1, name: 'hello world', reference_id: 2, display_priority: 40}, // 子项,display_priority: 40
]

可以看到,id: 3是第一个父项(display_priority: 10),其子项id: 5(reference_id: 3)紧随其后。接着是id: 4(display_priority: 80)和id: 2(display_priority: 100)这两个顶层父项,它们之间按display_priority排序。最后是id: 2的子项id: 6和id: 1,它们也按display_priority排序。

核心挑战分析

要实现这种复杂的重排序,我们面临以下几个主要挑战:

  1. 父子关系的建立与维护: 需要高效地识别每个元素的父项或子项,并能够快速地找到特定父项的所有子项。
  2. 多维度排序: 既要考虑父子层级,又要考虑display_priority。简单的sort函数无法一次性解决。
  3. 动态插入: 在构建最终数组时,需要将子项动态插入到其父项之后,而不是简单地追加。

原始尝试的reduce方法虽然试图解决父子插入问题,但它存在局限性:它依赖于父项在数组中出现的顺序,如果父项在子项之后,则可能无法正确插入。更重要的是,它没有完全处理display_priority的排序逻辑。

分步解决方案

为了克服上述挑战,我们将采用一个结构化的分步方法:

酷兔AI论文
酷兔AI论文

专业原创高质量、低查重,免费论文大纲,在线AI生成原创论文,AI辅助生成论文的神器!

下载

步骤一:构建数据索引与分类

首先,我们需要对原始数据进行预处理,以便快速查找元素及其子项,并将所有元素分为顶层父项和子项。

  1. 创建父项映射: 使用一个Map来存储所有父项(reference_id为null)的id到其完整对象的映射,以及一个列表来存储顶层父项。
  2. 创建子项映射: 使用另一个Map来存储每个父项id对应的所有子项列表。
// 示例数据
const rawData = [
  { id: 3, name: 'hello world', reference_id: null, display_priority: 10},
  { id: 6, name: 'hello world', reference_id: 2 , display_priority: 30},
  { id: 1, name: 'hello world', reference_id: 2, display_priority: 40 },
  { id: 4, name: 'hello world', reference_id: null, display_priority: 80},
  { id: 2, name: 'hello world', reference_id: null, display_priority: 100 },
  { id: 5, name: 'hello world', reference_id: 3, display_priority: 110 },
];

const parents = []; // 存储所有顶层父项
const childrenMap = new Map(); // 存储父项ID到其子项列表的映射

rawData.forEach(item => {
  if (item.reference_id === null) {
    parents.push(item);
  } else {
    if (!childrenMap.has(item.reference_id)) {
      childrenMap.set(item.reference_id, []);
    }
    childrenMap.get(item.reference_id).push(item);
  }
});

步骤二:应用显示优先级排序

在构建最终数组之前,我们需要确保所有顶层父项和每个父项下的子项都已根据display_priority进行排序。

  1. 排序顶层父项: 对parents数组进行display_priority升序排序。
  2. 排序子项列表: 遍历childrenMap中的每个子项列表,并对它们进行display_priority升序排序。
// 排序顶层父项
parents.sort((a, b) => a.display_priority - b.display_priority);

// 排序每个父项下的子项
childrenMap.forEach(childList => {
  childList.sort((a, b) => a.display_priority - b.display_priority);
});

步骤三:构建最终有序数组

现在我们有了排序好的顶层父项和每个父项下排序好的子项列表,可以开始构建最终的有序数组。

遍历排序后的parents数组。对于每个父项,将其添加到结果数组中,然后查找其在childrenMap中对应的子项列表,并将这些子项也依次添加到结果数组中。

const reorderedArray = [];

parents.forEach(parent => {
  reorderedArray.push(parent); // 添加父项
  if (childrenMap.has(parent.id)) {
    const sortedChildren = childrenMap.get(parent.id);
    reorderedArray.push(...sortedChildren); // 添加排序后的子项
  }
});

JavaScript 示例代码

将上述步骤整合到一个函数中,可以得到一个完整的解决方案:

/**
 * 根据父子关系和显示优先级对数组进行重排序。
 * 父项通过 reference_id: null 标识,子项通过 reference_id 关联父项 id。
 * 所有同级元素(包括顶层父项和同一父项下的子项)按 display_priority 升序排列。
 *
 * @param {Array} data 原始数据数组,每个对象需包含 id, reference_id, display_priority。
 * @returns {Array} 重排序后的数组。
 */
function reorderArrayByParentAndPriority(data) {
  if (!data || data.length === 0) {
    return [];
  }

  const parents = []; // 存储所有顶层父项
  const childrenMap = new Map(); // 存储父项ID到其子项列表的映射

  // 步骤一:构建数据索引与分类
  data.forEach(item => {
    // 确保 display_priority 存在且为数字,否则默认为一个大值以防排序异常
    const priority = typeof item.display_priority === 'number' ? item.display_priority : Infinity;
    const itemWithPriority = { ...item, display_priority: priority };

    if (item.reference_id === null) {
      parents.push(itemWithPriority);
    } else {
      if (!childrenMap.has(item.reference_id)) {
        childrenMap.set(item.reference_id, []);
      }
      childrenMap.get(item.reference_id).push(itemWithPriority);
    }
  });

  // 步骤二:应用显示优先级排序
  // 排序顶层父项
  parents.sort((a, b) => a.display_priority - b.display_priority);

  // 排序每个父项下的子项
  childrenMap.forEach(childList => {
    childList.sort((a, b) => a.display_priority - b.display_priority);
  });

  // 步骤三:构建最终有序数组
  const reorderedArray = [];
  parents.forEach(parent => {
    reorderedArray.push(parent); // 添加父项
    if (childrenMap.has(parent.id)) {
      const sortedChildren = childrenMap.get(parent.id);
      reorderedArray.push(...sortedChildren); // 添加排序后的子项
    }
  });

  return reorderedArray;
}

// 原始数据
const rawData = [
  { id: 3, name: 'hello world', reference_id: null, display_priority: 10},
  { id: 6, name: 'hello world', reference_id: 2 , display_priority: 30},
  { id: 1, name: 'hello world', reference_id: 2, display_priority: 40 },
  { id: 4, name: 'hello world', reference_id: null, display_priority: 80},
  { id: 2, name: 'hello world', reference_id: null, display_priority: 100 },
  { id: 5, name: 'hello world', reference_id: 3, display_priority: 110 },
];

const result = reorderArrayByParentAndPriority(rawData);
console.log(result);

/*
期望输出:
[
  { id: 3, name: 'hello world', reference_id: null, display_priority: 10 },
  { id: 5, name: 'hello world', reference_id: 3, display_priority: 110 },
  { id: 4, name: 'hello world', reference_id: null, display_priority: 80 },
  { id: 2, name: 'hello world', reference_id: null, display_priority: 100 },
  { id: 6, name: 'hello world', reference_id: 2, display_priority: 30 },
  { id: 1, name: 'hello world', reference_id: 2, display_priority: 40 }
]
*/

代码解析与注意事项

  1. 数据预处理与健壮性: 在forEach循环中,我们添加了对display_priority的类型检查。如果display_priority不存在或不是数字,我们将其视为Infinity。这确保了排序的健壮性,防止因数据不规范而导致程序崩溃,并将这些项推到排序结果的末尾。
  2. Map的优势: 使用Map (childrenMap) 来存储子项,可以实现 O(1) 的平均时间复杂度来查找特定父项的所有子项,这比每次遍历数组查找要高效得多,尤其是在数据量较大时。
  3. 分步排序的清晰性: 将分类、排序和重构三个逻辑步骤分开处理,使得代码逻辑更加清晰,易于理解和维护。
  4. push与spread操作符: reorderedArray.push(parent)用于添加单个父项,而reorderedArray.push(...sortedChildren)则利用展开运算符(spread operator)将一个数组的所有元素一次性添加到另一个数组中,这比循环push更简洁高效。
  5. 性能考虑:
    • 时间复杂度: 初始遍历分类的时间复杂度是 O(N)。排序顶层父项和所有子项列表的总时间复杂度取决于子项的数量和分布,最坏情况下(所有元素都是顶层父项或所有子项都在一个父项下)接近 O(N log N)。最后重构数组的时间复杂度是 O(N)。因此,整体时间复杂度为 O(N log N),对于大多数应用场景来说是高效的。
    • 空间复杂度: parents数组、childrenMap以及最终的reorderedArray都需要额外的空间,空间复杂度为 O(N)。

总结

通过本教程,我们学习了如何处理一个常见的复杂数组重排序问题,即同时考虑父子关系和显示优先级。关键在于将问题分解为几个可管理的步骤:首先进行数据索引和分类,然后对各个层级的数据独立进行优先级排序,最后将这些排序好的数据结构化地组合起来。这种分步处理的方法不仅使代码逻辑清晰,而且在处理大规模数据时也能保持较好的性能。这种策略可以推广到更复杂的树形结构排序场景,只需递归地应用类似的分层排序思想。

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

557

2023.06.20

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

374

2023.07.04

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

754

2023.07.04

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

478

2023.09.01

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

434

2023.09.04

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

1031

2023.09.04

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

658

2023.09.12

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

553

2023.09.20

Java编译相关教程合集
Java编译相关教程合集

本专题整合了Java编译相关教程,阅读专题下面的文章了解更多详细内容。

5

2026.01.21

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 3.9万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 2.3万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 2.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号