You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

数组元素移位方法及二级层级待办事项依赖排序问题求解

嘿,我来帮你搞定这两个问题!

问题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

火山引擎 最新活动