数组元素移位方法及二级层级待办事项依赖排序问题求解
嘿,我来帮你搞定这两个问题!
问题1:如何将数组中指定位置后的元素进行移动并移位?
首先得明确你具体的需求场景,不同的移位需求实现方式不一样,我给你几个常见场景的解决方案:
场景1:把指定位置及之后的所有元素整体移到数组开头
比如你有数组[1,2,3,4,5],想把索引2(元素3)之后的所有元素移到开头,得到[3,4,5,1,2],可以这么写:
function moveElementsToStart(arr, startIndex) { const movedSegment = arr.splice(startIndex); arr.unshift(...movedSegment); return arr; } // 测试 const testArr = [1,2,3,4,5]; console.log(moveElementsToStart(testArr, 2)); // 输出 [3,4,5,1,2]
场景2:将单个元素从原位置移动到指定位置,其余元素自动移位
这其实是数组元素移动的基础需求,用splice就能轻松实现:
function moveSingleElement(arr, oldIndex, newIndex) { // 边界判断,避免索引越界 if (oldIndex < 0 || oldIndex >= arr.length || newIndex < 0) return [...arr]; const [target] = arr.splice(oldIndex, 1); arr.splice(newIndex, 0, target); return arr; } // 测试 const arr = ['a','b','c','d']; console.log(moveSingleElement(arr, 3, 1)); // 输出 ['a','d','b','c']
场景3:指定位置后的元素循环移位(比如右移1位)
如果是想让指定位置后的元素整体循环右移,比如把[1,2,3,4,5]中索引2之后的元素右移1位,得到[1,2,5,3,4]:
function shiftSegment(arr, startIndex, shiftCount) { const segment = arr.splice(startIndex); // 计算移位后的片段 const shifted = [...segment.slice(-shiftCount), ...segment.slice(0, segment.length - shiftCount)]; arr.push(...shifted); return arr; } // 测试 const testArr2 = [1,2,3,4,5]; console.log(shiftSegment(testArr2, 2, 1)); // 输出 [1,2,5,3,4]
问题2:带依赖关系的待办事项列表排序
你的核心问题是依赖嵌套的任务排序,用swapArrayLocs逐个移动元素的方式很容易因为顺序问题出错,尤其是存在多层依赖(比如示例中6依赖3,3依赖1)的时候。更可靠的方式是先构建任务的依赖树,再通过遍历树来生成正确的顺序。
推荐方案:构建依赖树+深度优先遍历
这个方法能完美保证父任务→子任务→子任务的子任务的顺序,而且支持扩展到更多层级:
function sortTaskList(tasks) { // 第一步:构建任务ID到任务对象的映射,方便快速查找 const taskMap = new Map(tasks.map(task => [task.id, task])); // 第二步:构建每个任务的子任务列表 const childrenMap = new Map(); tasks.forEach(task => { if (!childrenMap.has(task.id)) childrenMap.set(task.id, []); if (task.taskParentId !== null) { if (!childrenMap.has(task.taskParentId)) childrenMap.set(task.taskParentId, []); childrenMap.get(task.taskParentId).push(task); } }); // 第三步:深度优先遍历,生成排序后的数组 const sortedTasks = []; function traverse(task) { sortedTasks.push(task); // 遍历当前任务的所有子任务(这里可以加排序规则,比如按ID排序) const children = childrenMap.get(task.id) || []; children.forEach(child => traverse(child)); } // 先处理所有根任务(taskParentId为null的任务),可以按ID排序根任务 const rootTasks = tasks .filter(task => task.taskParentId === null) .sort((a, b) => a.id - b.id); rootTasks.forEach(root => traverse(root)); return sortedTasks; } // 测试你的示例数据 const data = [ {id: 1, taskParentId: null}, {id:2, taskParentId: null}, {id:3, taskParentId: 1}, {id:4, taskParentId: null}, {id:5, taskParentId: 2}, {id:6, taskParentId: 3} ]; const sortedResult = sortTaskList(data); console.log(sortedResult.map(t => t.id).join('-')); // 输出 "1-3-6-2-5-4",完全符合你的期望
为什么这个方法更好?
- 不需要手动调整元素位置,完全依赖任务的依赖关系生成顺序,不会出错
- 可以灵活调整排序规则,比如根任务按创建时间排序,子任务按优先级排序
- 支持任意层级的嵌套,哪怕以后需求扩展到三级、四级都能直接用
如果你只需要严格的两级结构(父任务后面只跟直接子任务,不包含子任务的子任务),只需要修改traverse函数,去掉递归即可:
function traverseTwoLevels(task) { sortedTasks.push(task); const children = childrenMap.get(task.id) || []; children.forEach(child => sortedTasks.push(child)); }
另外,如果你的数据集很小,也可以用路径比较的排序方式,代码更简洁:
// 获取任务的完整路径(比如id=6的路径是[1,3,6]) function getTaskPath(task, tasks) { const path = []; let current = task; while (current) { path.unshift(current.id); current = tasks.find(t => t.id === current.taskParentId); } return path; } function sortTasksByPath(tasks) { return tasks.sort((a, b) => { const pathA = getTaskPath(a, tasks); const pathB = getTaskPath(b, tasks); // 逐位比较路径,路径靠前的任务排在前面 for (let i = 0; i < Math.max(pathA.length, pathB.length); i++) { const numA = pathA[i] || 0; const numB = pathB[i] || 0; if (numA !== numB) return numA - numB; } return 0; }); } // 测试 console.log(sortTasksByPath(data).map(t => t.id).join('-')); // 同样输出 "1-3-6-2-5-4"
这个方法的缺点是每次排序都要遍历查找路径,数据量大的时候性能不如第一个方法,但胜在代码简洁。
内容的提问来源于stack exchange,提问作者Andy88




