如何实现带点分序列号的对象数组删除后的重排?
带点分版本序列号的数组删除与重排实现方案
示例数据
const sampleData = [ { name: 'Edward', serialNumber: '1' }, { name: 'Sharpe', serialNumber: '1.1' }, { name: 'And', serialNumber: '1.2' }, { name: 'The', serialNumber: '1.2.1' }, { name: 'Magnetic', serialNumber: '2' }, { name: 'Zeros', serialNumber: '2.1' }, { name: 'Zeros', serialNumber: '2.1.1' }, { name: 'shappa', serialNumber: '3' }, { name: 'Firo', serialNumber: '4' }, { name: 'Dholya', serialNumber: '4.1' }, { name: 'dhampya', serialNumber: '4.1.1' }, { name: 'SHivaji', serialNumber: '4.2' }, { name: 'Jyoti', serialNumber: '4.2.1' }, { name: 'Shr', serialNumber: '4.2.2' } ];
删除与重排需求
- 场景1:删除序列号为
'3'的对象后,原序列号4及所有子节点的序列号调整为3及对应子级(比如4→3,4.1→3.1,以此类推) - 场景2:删除序列号为
'4'的对象后,同时移除其所有子节点(即删除4、4.1、4.1.1、4.2、4.2.1、4.2.2) - 场景3:删除序列号为
'4.1'的对象及其子节点后,原4.2及子节点的序列号调整为4.1及对应子级(比如4.2→4.1,4.2.1→4.1.1)
最优实现方案
核心思路
- 先筛选出需要保留的节点:排除目标节点及其所有子节点
- 将保留的节点按层级排序,再按父节点分组重新生成连续的点分序列号
工具函数与核心实现
// 判断某序列号是否是目标序列号的子节点或自身 function isDescendantOrSelf(serial, targetSerial) { const targetParts = targetSerial.split('.').map(Number); const serialParts = serial.split('.').map(Number); if (serialParts.length < targetParts.length) return false; // 逐段匹配前缀,完全匹配则为目标节点或其子节点 for (let i = 0; i < targetParts.length; i++) { if (serialParts[i] !== targetParts[i]) return false; } return true; } // 对保留节点重新生成连续序列号 function renumberNodes(nodes) { // 按序列号层级和数值排序,确保父节点在前、子节点在后 nodes.sort((a, b) => { const aParts = a.serialNumber.split('.').map(Number); const bParts = b.serialNumber.split('.').map(Number); const maxLen = Math.max(aParts.length, bParts.length); for (let i = 0; i < maxLen; i++) { const aVal = aParts[i] || 0; const bVal = bParts[i] || 0; if (aVal !== bVal) return aVal - bVal; } return 0; }); // 记录每个父节点下的子节点计数,用于生成新序列号 const parentCounter = new Map(); const result = []; for (const node of nodes) { const originalParts = node.serialNumber.split('.').map(Number); // 生成父节点路径(去掉当前节点的最后一段序号) const parentPath = originalParts.slice(0, -1).join('.') || ''; // 获取当前父级下的子节点序号,初始为1 const currentCount = (parentCounter.get(parentPath) || 0) + 1; parentCounter.set(parentPath, currentCount); // 拼接新序列号 const newSerialParts = parentPath ? [...parentPath.split('.').map(Number), currentCount] : [currentCount]; const newSerial = newSerialParts.join('.'); result.push({ ...node, serialNumber: newSerial }); } return result; } // 主函数:删除目标节点并重排序列号 function deleteAndRenumber(originalData, targetSerial) { // 过滤掉目标节点及其所有子节点 const filteredNodes = originalData.filter(node => !isDescendantOrSelf(node.serialNumber, targetSerial)); // 对剩余节点重新编号 return renumberNodes(filteredNodes); }
场景验证
场景1:删除'3'
const result1 = deleteAndRenumber(sampleData, '3'); console.log(result1.map(n => ({name: n.name, serialNumber: n.serialNumber}))); // 输出: // [ // { name: 'Edward', serialNumber: '1' }, // { name: 'Sharpe', serialNumber: '1.1' }, // { name: 'And', serialNumber: '1.2' }, // { name: 'The', serialNumber: '1.2.1' }, // { name: 'Magnetic', serialNumber: '2' }, // { name: 'Zeros', serialNumber: '2.1' }, // { name: 'Zeros', serialNumber: '2.1.1' }, // { name: 'Firo', serialNumber: '3' }, // { name: 'Dholya', serialNumber: '3.1' }, // { name: 'dhampya', serialNumber: '3.1.1' }, // { name: 'SHivaji', serialNumber: '3.2' }, // { name: 'Jyoti', serialNumber: '3.2.1' }, // { name: 'Shr', serialNumber: '3.2.2' } // ]
场景2:删除'4'
const result2 = deleteAndRenumber(sampleData, '4'); console.log(result2.map(n => ({name: n.name, serialNumber: n.serialNumber}))); // 输出: // [ // { name: 'Edward', serialNumber: '1' }, // { name: 'Sharpe', serialNumber: '1.1' }, // { name: 'And', serialNumber: '1.2' }, // { name: 'The', serialNumber: '1.2.1' }, // { name: 'Magnetic', serialNumber: '2' }, // { name: 'Zeros', serialNumber: '2.1' }, // { name: 'Zeros', serialNumber: '2.1.1' }, // { name: 'shappa', serialNumber: '3' } // ]
场景3:删除'4.1'
const result3 = deleteAndRenumber(sampleData, '4.1'); console.log(result3.map(n => ({name: n.name, serialNumber: n.serialNumber}))); // 输出: // [ // { name: 'Edward', serialNumber: '1' }, // { name: 'Sharpe', serialNumber: '1.1' }, // { name: 'And', serialNumber: '1.2' }, // { name: 'The', serialNumber: '1.2.1' }, // { name: 'Magnetic', serialNumber: '2' }, // { name: 'Zeros', serialNumber: '2.1' }, // { name: 'Zeros', serialNumber: '2.1.1' }, // { name: 'shappa', serialNumber: '3' }, // { name: 'Firo', serialNumber: '4' }, // { name: 'SHivaji', serialNumber: '4.1' }, // { name: 'Jyoti', serialNumber: '4.1.1' }, // { name: 'Shr', serialNumber: '4.1.2' } // ]
方案优势
- 通用性:一套代码覆盖所有三种场景,无需针对不同层级编写单独逻辑
- 稳定性:先排序再重编号,确保父节点始终在子节点之前,避免编号混乱
- 可扩展性:支持任意深度的点分序列号,新增层级无需修改核心逻辑
内容的提问来源于stack exchange,提问作者A.K.
相关产品推荐
相关产品推荐

