特定情况在这里指,单条数据在整个算法中仅需做一次处理
在项目中遇到如下需求,将一条内部有依赖关系的扁平结构数据,转换为树形结构数据,其中属性 pid 对应其父节点的属性 id,属性 children 存放子节点,顶层(一级)pid 为 0
首先想到了如下解决方法
function flatToTree(list, id) { // 数据树形化 // 参数 list 表示原始扁平结构数据 // 参数 id 用来标识顶层 pid const result = [] list.forEach(item => { if (item.pid === id) { const children = flatToTree(list, item.id) if (children.length) item.children = children // 规避无子节点的数据携带不必要的 children 空数组 result.push(item) } }) return result }
后面觉察到每条数据在遍历中被匹配时,后面的遍历过程中其实已经无需此条数据的参与了,于是想了想做了如下改进
function flatToTree(list, id) { // 数据树形化 // 参数 list 表示原始扁平结构数据 // 参数 id 用来标识顶层 pid const result = [] for (let i = 0; i < list.length; i++) { if (list[i].pid === id) { const temp = { ...list[i] } // 临时变量接收匹配到的数据 list.splice(i, 1) // 在原数组中删除此条数据 i-- // 遍历回跳 const children = flatToTree(list, temp.id) if (children.length) temp.children = children // 规避无子节点的数据携带不必要的 children 空数组 result.push(temp) } } return result }
由于上面的树形结构数据需求至多到二级,且数量并不多,所以为了深入考察优化的可靠性,做了如下测试
先看测试结果吧( 原始数据 10,000 条 )
// 测试数据树形化的递归次数优化 // -------------------------------------- 定义相关函数 -------------------------------------- // ---------- 获取随机数 function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min } // ---------- 生成原始数据 function getData(intNum = 20, top = 5) { // 参数 intNum 表示生成数据的长度 // 参数 top 表示顶层(一级)的数量 const data = [] for (let i = 1; i <= intNum; i++) { let item = { id: i, pid: getRandomInt(1, i - 1) // 规避id与pid相等的情况 } if (i < top) { item.pid = 0 } data.push(item) } return data } // ---------- 数据树形化 function flatToTree(list, id, complete = true, num) { // 参数 id 用来标识顶层 pid // 参数 complete 表示选择的递归方法 // 参数 num 表示计数 const result = [] if (complete) { // 完整递归 list.forEach(item => { num[0]++ // 计数 if (item.pid === id) { const children = flatToTree(list, item.id, true, num) if (children.length) item.children = children // 规避无子节点的数据携带不必要的 children 空数组 result.push(item) } }) } else { // 递归次数优化 for (let i = 0; i < list.length; i++) { num[0]++ // 计数 if (list[i].pid === id) { const temp = { ...list[i] } list.splice(i, 1) i-- const children = flatToTree(list, temp.id, false, num) if (children.length) temp.children = children result.push(temp) } } } return result } // ---------- 深度提取数组中的对象,平级罗列,检验树形化后的数据是否完整 function resolveObjectFromArray(arr, res) { arr.forEach(item => { if (item.children) { res.push({ id: item.id, pid: item.pid }) resolveObjectFromArray(item.children, res) } else { res.push(item) } }) } // ---------- 测试生成 function multiTest(complete = true) { // 参数 complete,true of false 完整递归 or 减法递归 let num = [0] // 递归计数器 // const data = [...resourceData] // 减小影响结果的可能性,生成新的地址存放数据 const data = [] resourceData.forEach(item => { data.push({ ...item }) }) const timeBefore = +new Date() const res = flatToTree(data, 0, complete, num) // 数据树形化 const timeAfter = +new Date() console.log('时间消耗:', (timeAfter - timeBefore) / 1000); // 打印时间差,秒 console.log('树形结构数据:', res); console.log('次数:', num); // 打印计数器 let flatRes = [] // 存放检验结果 resolveObjectFromArray(res, flatRes) // 检验数据完整性 console.log('树形结构还原:', flatRes); } // --------------------------------------测试开始 -------------------------------------- const resourceData = getData(10_000, 5) // 生成原始数据,这里按 10,000 条数据了来进行测试 console.log('resourceData == ', resourceData); // 测试开始 console.log('---------------------------------------- 完整递归 ------------------------------------------------') multiTest() // 完整递归 console.log('---------------------------------------- 递归优化 ------------------------------------------------') multiTest(false) // 递归优化