
在前端或后端数据处理中,我们经常会遇到需要对具有层级关系的数据进行排序的场景。例如,一个产品列表可能包含主产品和其关联的子产品,同时每个产品都有一个显示优先级。我们的目标是根据以下规则对一个扁平化数组进行重排序:
给定以下原始数据结构:
const originalData = [
{ 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 },
];我们期望得到的输出顺序如下:
[
{ id: 3, name: 'hello world', reference_id: null, display_priority: 10 },
{ id: 5, name: 'hello world', reference_id: 3, display_priority: 110 }, // id:5 是 id:3 的子项
{ 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:6 是 id:2 的子项
{ id: 1, name: 'hello world', reference_id: 2, display_priority: 40 }, // id:1 也是 id:2 的子项
];可以看到,id: 3 作为父项,其 display_priority 为 10。其子项 id: 5 紧随其后。 然后是顶层项 id: 4 (priority 80),接着是 id: 2 (priority 100)。 id: 2 的子项 id: 6 (priority 30) 和 id: 1 (priority 40) 紧随其后,且它们之间按 display_priority 排序。
用户最初尝试使用 Array.prototype.reduce 方法来迭代并构建排序后的数组:
const reorderedArray = test.reduce((acc, current) => {
const referenceId = current.reference_id;
if (referenceId === null) {
const referencedChildIndex = acc.findIndex(item => item.reference_id === current.id);
if (referencedChildIndex !== -1) {
acc.splice(referencedChildIndex, 0, current);
} else {
acc.push(current);
}
} else {
const referencedIndex = acc.findIndex(item => item.id === referenceId);
if (referencedIndex !== -1) {
acc.splice(referencedIndex + 1, 0, current);
} else {
acc.push(current);
}
}
return acc;
}, []);这种方法的主要局限在于它高度依赖于原始数组中元素的顺序。如果父元素在原始数组中出现在其子元素之后,或者子元素在父元素之前被处理,findIndex 可能无法找到对应的父/子项,导致元素被错误地放置到数组末尾。此外,这种方法并未充分考虑 display_priority 的排序逻辑,仅侧重于父子关系的插入。对于更复杂的层级结构和多重排序条件,这种迭代插入的方式难以保证最终结果的正确性和效率。
为了可靠地解决这个问题,我们需要一个更结构化的方法,它将分三个主要阶段进行:构建ID映射表、构建层级结构、排序层级结构 和 展平层级结构。
首先,创建一个哈希映射(Map 或对象),将每个元素的 id 作为键,元素本身作为值。这使得我们能够以 O(1) 的时间复杂度通过 id 快速查找任何元素,这在构建父子关系时非常有用。
遍历原始数组中的每个元素。对于每个元素,我们将其视为一个潜在的节点。如果一个元素的 reference_id 不为 null,则说明它是一个子节点,需要将其添加到其父节点的 children 数组中。同时,我们需要识别出所有 reference_id 为 null 的顶层节点。
一旦构建了树状的层级结构(每个父节点包含一个 children 数组),我们就需要对这些结构进行排序。这通常通过递归完成:
最后一步是将排序后的树状结构展平回一个一维数组。这可以通过深度优先遍历(DFS)的递归方式实现:
下面是使用 JavaScript 实现上述思路的完整代码:
function reorderArrayByReferenceAndPriority(data) {
// 1. 构建ID映射表,并为每个项添加一个空的children数组
const itemMap = new Map();
data.forEach(item => {
// 创建一个副本,避免直接修改原始数据,并添加children属性
itemMap.set(item.id, { ...item, children: [] });
});
const topLevelItems = [];
// 2. 构建层级结构
itemMap.forEach(item => {
if (item.reference_id === null) {
// 顶级项
topLevelItems.push(item);
} else {
// 子项,尝试找到其父项
const parent = itemMap.get(item.reference_id);
if (parent) {
parent.children.push(item);
} else {
// 如果找不到父项,将其视为顶级项(或根据业务需求处理,例如报错)
topLevelItems.push(item);
console.warn(`Item with id ${item.id} has reference_id ${item.reference_id}, but parent not found.`);
}
}
});
// 3. 排序层级结构
// 递归函数,用于对子节点进行排序
function sortChildren(node) {
if (node.children && node.children.length > 0) {
node.children.sort((a, b) => (a.display_priority || 0) - (b.display_priority || 0));
node.children.forEach(child => sortChildren(child)); // 递归排序子节点的子节点
}
}
// 对所有顶级项的子节点进行排序
topLevelItems.forEach(item => sortChildren(item));
// 对顶级项本身进行排序
topLevelItems.sort((a, b) => (a.display_priority || 0) - (b.display_priority || 0));
// 4. 展平层级结构
const reorderedFlatArray = [];
// 递归函数,用于展平树结构
function flatten(node) {
// 添加当前节点到结果数组中,但移除临时的children属性
const { children, ...itemWithoutChildren } = node;
reorderedFlatArray.push(itemWithoutChildren);
if (children && children.length > 0) {
children.forEach(child => flatten(child));
}
}
// 遍历排序后的顶级项,并展平
topLevelItems.forEach(item => flatten(item));
return reorderedFlatArray;
}
// 原始数据
const originalData = [
{ 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 reorderedResult = reorderArrayByReferenceAndPriority(originalData);
console.log(JSON.stringify(reorderedResult, null, 2));输出结果:
[
{
"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
}
]itemMap 的构建与初始化:
构建层级结构:
排序层级结构:
展平层级结构:
通过将扁平化数组转换为一个临时的树状结构,并结合 Map 进行高效查找、递归排序以及深度优先遍历展平,我们能够可靠地实现根据父子关系和显示优先级对数组对象进行排序的需求。这种方法结构清晰、逻辑严谨,能够有效应对复杂的层级和多重排序条件,是处理此类数据排序问题的通用且健壮的解决方案。
以上就是数组对象根据父子关系与显示优先级进行排序的通用方法的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号